Ignore:
Timestamp:
2011-06-19T16:47:07+02:00 (13 years ago)
Author:
stoecker
Message:

fix toolbar entry

Location:
applications/editors/josm/plugins/turnlanes
Files:
26 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/turnlanes/build.xml

    r25607 r26154  
    3333        <property name="commit.message" value="Commit message" />
    3434        <!-- enter the *lowest* JOSM version this plugin is currently compatible with -->
    35         <property name="plugin.main.version" value="3518" />
    36 
     35        <property name="plugin.main.version" value="4126" />
    3736
    3837        <!--
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/CollectionUtils.java

    r25783 r26154  
    1010
    1111public class CollectionUtils {
    12         public static <E> Iterable<E> reverse(final List<E> list) {
    13                 return new Iterable<E>() {
    14                         @Override
    15                         public Iterator<E> iterator() {
    16                                 final ListIterator<E> it = list.listIterator(list.size());
    17                                
    18                                 return new Iterator<E>() {
    19                                         @Override
    20                                         public boolean hasNext() {
    21                                                 return it.hasPrevious();
    22                                         }
    23                                        
    24                                         @Override
    25                                         public E next() {
    26                                                 return it.previous();
    27                                         }
    28                                        
    29                                         @Override
    30                                         public void remove() {
    31                                                 it.remove();
    32                                         }
    33                                 };
    34                         }
    35                 };
    36         }
    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         }
     12    public static <E> Iterable<E> reverse(final List<E> list) {
     13        return new Iterable<E>() {
     14            @Override
     15            public Iterator<E> iterator() {
     16                final ListIterator<E> it = list.listIterator(list.size());
     17               
     18                return new Iterator<E>() {
     19                    @Override
     20                    public boolean hasNext() {
     21                        return it.hasPrevious();
     22                    }
     23                   
     24                    @Override
     25                    public E next() {
     26                        return it.previous();
     27                    }
     28                   
     29                    @Override
     30                    public void remove() {
     31                        it.remove();
     32                    }
     33                };
     34            }
     35        };
     36    }
     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    }
    5757}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/TurnLanesPlugin.java

    r25606 r26154  
    77
    88public class TurnLanesPlugin extends Plugin {
    9         public TurnLanesPlugin(PluginInformation info) {
    10                 super(info);
    11         }
    12        
    13         @Override
    14         public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
    15                 if (oldFrame == null && newFrame != null) {
    16                         // there was none before
    17                         newFrame.addToggleDialog(new TurnLanesDialog());
    18                 }
    19         }
     9    public TurnLanesPlugin(PluginInformation info) {
     10        super(info);
     11    }
     12
     13    @Override
     14    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
     15        if (oldFrame == null && newFrame != null) {
     16            // there was none before
     17            newFrame.addToggleDialog(new TurnLanesDialog());
     18        }
     19    }
    2020}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiContainer.java

    r25783 r26154  
    2424
    2525class GuiContainer {
    26         static final Color RED = new Color(234, 66, 108);
    27         static final Color GREEN = new Color(66, 234, 108);
    28        
    29         private final ModelContainer mc;
    30        
    31         private final Point2D translation;
    32         /**
    33         * Meters per pixel.
    34         */
    35         private final double mpp;
    36         private final double scale;
    37         private final double laneWidth;
    38        
    39         private final Map<Junction, JunctionGui> junctions = new HashMap<Junction, JunctionGui>();
    40         private final Map<Road, RoadGui> roads = new HashMap<Road, RoadGui>();
    41        
    42         private final Stroke connectionStroke;
    43        
    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;
    54                 this.translation = new Point2D.Double(-origin.getX(), -origin.getY());
    55                 this.mpp = 0.2;
    56                 this.scale = mpsu / mpp;
    57                 this.laneWidth = 2 / mpp;
    58                
    59                 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());
    76         }
    77        
    78         public JunctionGui getGui(Junction j) {
    79                 final JunctionGui existing = junctions.get(j);
    80                 if (existing != null) {
    81                         return existing;
    82                 }
    83                
    84                 return new JunctionGui(this, j);
    85         }
    86        
    87         void register(JunctionGui j) {
    88                 if (junctions.put(j.getModel(), j) != null) {
    89                         throw new IllegalStateException();
    90                 }
    91         }
    92        
    93         public RoadGui getGui(Road r) {
    94                 final RoadGui gui = roads.get(r);
    95                
    96                 if (gui == null) {
    97                         final RoadGui newGui = new RoadGui(this, r);
    98                         roads.put(r, newGui);
    99                         return newGui;
    100                 }
    101                
    102                 return gui;
    103         }
    104        
    105         Point2D translateAndScale(Point2D loc) {
    106                 return new Point2D.Double((loc.getX() + translation.getX()) * scale, (loc.getY() + translation.getY()) * scale);
    107         }
    108        
    109         /**
    110         * @return meters per pixel
    111         */
    112         public double getMpp() {
    113                 return mpp;
    114         }
    115        
    116         public double getScale() {
    117                 return scale;
    118         }
    119        
    120         public double getLaneWidth() {
    121                 return laneWidth;
    122         }
    123        
    124         public Stroke getConnectionStroke() {
    125                 return connectionStroke;
    126         }
    127        
    128         public LaneGui getGui(Lane lane) {
    129                 final RoadGui roadGui = roads.get(lane.getRoad());
    130                
    131                 for (LaneGui l : roadGui.getLanes()) {
    132                         if (l.getModel().equals(lane)) {
    133                                 return l;
    134                         }
    135                 }
    136                
    137                 throw new IllegalArgumentException("No such lane.");
    138         }
    139        
    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();
    179         }
     26    static final Color RED = new Color(234, 66, 108);
     27    static final Color GREEN = new Color(66, 234, 108);
     28   
     29    private final ModelContainer mc;
     30   
     31    private final Point2D translation;
     32    /**
     33    * Meters per pixel.
     34    */
     35    private final double mpp;
     36    private final double scale;
     37    private final double laneWidth;
     38   
     39    private final Map<Junction, JunctionGui> junctions = new HashMap<Junction, JunctionGui>();
     40    private final Map<Road, RoadGui> roads = new HashMap<Road, RoadGui>();
     41   
     42    private final Stroke connectionStroke;
     43   
     44    public GuiContainer(ModelContainer mc) {
     45        final Point2D origin = avgOrigin(locs(mc.getPrimaryJunctions()));
     46       
     47        final LatLon originCoor = Main.getProjection().eastNorth2latlon(new EastNorth(origin.getX(), origin.getY()));
     48        final LatLon relCoor = Main.getProjection().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;
     54        this.translation = new Point2D.Double(-origin.getX(), -origin.getY());
     55        this.mpp = 0.2;
     56        this.scale = mpsu / mpp;
     57        this.laneWidth = 2 / mpp;
     58       
     59        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());
     76    }
     77   
     78    public JunctionGui getGui(Junction j) {
     79        final JunctionGui existing = junctions.get(j);
     80        if (existing != null) {
     81            return existing;
     82        }
     83       
     84        return new JunctionGui(this, j);
     85    }
     86   
     87    void register(JunctionGui j) {
     88        if (junctions.put(j.getModel(), j) != null) {
     89            throw new IllegalStateException();
     90        }
     91    }
     92   
     93    public RoadGui getGui(Road r) {
     94        final RoadGui gui = roads.get(r);
     95       
     96        if (gui == null) {
     97            final RoadGui newGui = new RoadGui(this, r);
     98            roads.put(r, newGui);
     99            return newGui;
     100        }
     101       
     102        return gui;
     103    }
     104   
     105    Point2D translateAndScale(Point2D loc) {
     106        return new Point2D.Double((loc.getX() + translation.getX()) * scale, (loc.getY() + translation.getY()) * scale);
     107    }
     108   
     109    /**
     110    * @return meters per pixel
     111    */
     112    public double getMpp() {
     113        return mpp;
     114    }
     115   
     116    public double getScale() {
     117        return scale;
     118    }
     119   
     120    public double getLaneWidth() {
     121        return laneWidth;
     122    }
     123   
     124    public Stroke getConnectionStroke() {
     125        return connectionStroke;
     126    }
     127   
     128    public LaneGui getGui(Lane lane) {
     129        final RoadGui roadGui = roads.get(lane.getRoad());
     130       
     131        for (LaneGui l : roadGui.getLanes()) {
     132            if (l.getModel().equals(lane)) {
     133                return l;
     134            }
     135        }
     136       
     137        throw new IllegalArgumentException("No such lane.");
     138    }
     139   
     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();
     179    }
    180180}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiUtil.java

    r25783 r26154  
    1717
    1818class GuiUtil {
    19         static double normalize(double a) {
    20                 while (a < 0) {
    21                         a += 2 * Math.PI;
    22                 }
    23                 while (a > 2 * Math.PI) {
    24                         a -= 2 * Math.PI;
    25                 }
    26                 return a;
    27         }
    28        
    29         // control point factor for curves (circle segment of angle a)
    30         static double cpf(double a, double scale) {
    31                 return 4.0 / 3 * Math.tan(min(abs(a), PI - 0.001) / 4) * scale;
    32         }
    33        
    34         static Point2D intersection(Line2D a, Line2D b) {
    35                 final double aa = GuiUtil.angle(a);
    36                 final double ab = GuiUtil.angle(b);
    37                
    38                 // less than 1/2 degree => no intersection
    39                 if (Math.abs(Math.PI - abs(minAngleDiff(aa, ab))) < PI / 360) {
    40                         return null;
    41                 }
    42                
    43                 final double d = (a.getX1() - a.getX2()) * (b.getY1() - b.getY2()) - (a.getY1() - a.getY2())
    44                     * (b.getX1() - b.getX2());
    45                
    46                 final double x = ((b.getX1() - b.getX2()) * (a.getX1() * a.getY2() - a.getY1() * a.getX2()) - (a.getX1() - a
    47                     .getX2()) * (b.getX1() * b.getY2() - b.getY1() * b.getX2()))
    48                     / d;
    49                 final double y = ((b.getY1() - b.getY2()) * (a.getX1() * a.getY2() - a.getY1() * a.getX2()) - (a.getY1() - a
    50                     .getY2()) * (b.getX1() * b.getY2() - b.getY1() * b.getX2()))
    51                     / d;
    52                
    53                 return new Point2D.Double(x, y);
    54         }
    55        
    56         static Point2D closest(Line2D l, Point2D p) {
    57                 final Point2D lv = vector(l.getP1(), l.getP2());
    58                 final double numerator = dot(vector(l.getP1(), p), lv);
    59                
    60                 if (numerator < 0) {
    61                         return l.getP1();
    62                 }
    63                
    64                 final double denominator = dot(lv, lv);
    65                 if (numerator >= denominator) {
    66                         return l.getP2();
    67                 }
    68                
    69                 final double r = numerator / denominator;
    70                 return new Point2D.Double(l.getX1() + r * lv.getX(), l.getY1() + r * lv.getY());
    71         }
    72        
    73         private static double dot(Point2D a, Point2D b) {
    74                 return a.getX() * b.getX() + a.getY() * b.getY();
    75         }
    76        
    77         private static Point2D vector(Point2D from, Point2D to) {
    78                 return new Point2D.Double(to.getX() - from.getX(), to.getY() - from.getY());
    79         }
    80        
    81         public static double angle(Point2D from, Point2D to) {
    82                 final double dx = to.getX() - from.getX();
    83                 final double dy = -(to.getY() - from.getY());
    84                
    85                 return normalize(Math.atan2(dy, dx));
    86         }
    87        
    88         public static Point2D relativePoint(Point2D p, double r, double a) {
    89                 return new Point2D.Double( //
    90                     p.getX() + r * Math.cos(a), //
    91                     p.getY() - r * Math.sin(a) //
    92                 );
    93         }
    94        
    95         public static Line2D relativeLine(Line2D l, double r, double a) {
    96                 final double dx = r * Math.cos(a);
    97                 final double dy = -r * Math.sin(a);
    98                
    99                 return new Line2D.Double( //
    100                     l.getX1() + dx, //
    101                     l.getY1() + dy, //
    102                     l.getX2() + dx, //
    103                     l.getY2() + dy //
    104                 );
    105         }
    106        
    107         public static double angle(Line2D l) {
    108                 return angle(l.getP1(), l.getP2());
    109         }
    110        
    111         public static double minAngleDiff(double a1, double a2) {
    112                 final double d = normalize(a2 - a1);
    113                
    114                 return d > Math.PI ? -(2 * Math.PI - d) : d;
    115         }
    116        
    117         public static final Point2D middle(Point2D a, Point2D b) {
    118                 return relativePoint(a, a.distance(b) / 2, angle(a, b));
    119         }
    120        
    121         public static final Point2D middle(Line2D l) {
    122                 return middle(l.getP1(), l.getP2());
    123         }
    124        
    125         public static Line2D line(Point2D p, double a) {
    126                 return new Line2D.Double(p, relativePoint(p, 1, a));
    127         }
    128        
    129         public static Point2D loc(Node node) {
    130                 final EastNorth loc = Main.proj.latlon2eastNorth(node.getCoor());
    131                 return new Point2D.Double(loc.getX(), -loc.getY());
    132         }
    133        
    134         public static List<Point2D> locs(Iterable<Junction> junctions) {
    135                 final List<Point2D> locs = new ArrayList<Point2D>();
    136                
    137                 for (Junction j : junctions) {
    138                         locs.add(loc(j.getNode()));
    139                 }
    140                
    141                 return locs;
    142         }
    143        
    144         static void area(Path2D area, Path inner, Path outer) {
    145                 area.append(inner.getIterator(), false);
    146                 area.append(ReversePathIterator.reverse(outer.getIterator()), true);
    147                 area.closePath();
    148         }
     19    static double normalize(double a) {
     20        while (a < 0) {
     21            a += 2 * Math.PI;
     22        }
     23        while (a > 2 * Math.PI) {
     24            a -= 2 * Math.PI;
     25        }
     26        return a;
     27    }
     28   
     29    // control point factor for curves (circle segment of angle a)
     30    static double cpf(double a, double scale) {
     31        return 4.0 / 3 * Math.tan(min(abs(a), PI - 0.001) / 4) * scale;
     32    }
     33   
     34    static Point2D intersection(Line2D a, Line2D b) {
     35        final double aa = GuiUtil.angle(a);
     36        final double ab = GuiUtil.angle(b);
     37       
     38        // less than 1/2 degree => no intersection
     39        if (Math.abs(Math.PI - abs(minAngleDiff(aa, ab))) < PI / 360) {
     40            return null;
     41        }
     42       
     43        final double d = (a.getX1() - a.getX2()) * (b.getY1() - b.getY2()) - (a.getY1() - a.getY2())
     44            * (b.getX1() - b.getX2());
     45       
     46        final double x = ((b.getX1() - b.getX2()) * (a.getX1() * a.getY2() - a.getY1() * a.getX2()) - (a.getX1() - a
     47            .getX2()) * (b.getX1() * b.getY2() - b.getY1() * b.getX2()))
     48            / d;
     49        final double y = ((b.getY1() - b.getY2()) * (a.getX1() * a.getY2() - a.getY1() * a.getX2()) - (a.getY1() - a
     50            .getY2()) * (b.getX1() * b.getY2() - b.getY1() * b.getX2()))
     51            / d;
     52       
     53        return new Point2D.Double(x, y);
     54    }
     55   
     56    static Point2D closest(Line2D l, Point2D p) {
     57        final Point2D lv = vector(l.getP1(), l.getP2());
     58        final double numerator = dot(vector(l.getP1(), p), lv);
     59       
     60        if (numerator < 0) {
     61            return l.getP1();
     62        }
     63       
     64        final double denominator = dot(lv, lv);
     65        if (numerator >= denominator) {
     66            return l.getP2();
     67        }
     68       
     69        final double r = numerator / denominator;
     70        return new Point2D.Double(l.getX1() + r * lv.getX(), l.getY1() + r * lv.getY());
     71    }
     72   
     73    private static double dot(Point2D a, Point2D b) {
     74        return a.getX() * b.getX() + a.getY() * b.getY();
     75    }
     76   
     77    private static Point2D vector(Point2D from, Point2D to) {
     78        return new Point2D.Double(to.getX() - from.getX(), to.getY() - from.getY());
     79    }
     80   
     81    public static double angle(Point2D from, Point2D to) {
     82        final double dx = to.getX() - from.getX();
     83        final double dy = -(to.getY() - from.getY());
     84       
     85        return normalize(Math.atan2(dy, dx));
     86    }
     87   
     88    public static Point2D relativePoint(Point2D p, double r, double a) {
     89        return new Point2D.Double( //
     90            p.getX() + r * Math.cos(a), //
     91            p.getY() - r * Math.sin(a) //
     92        );
     93    }
     94   
     95    public static Line2D relativeLine(Line2D l, double r, double a) {
     96        final double dx = r * Math.cos(a);
     97        final double dy = -r * Math.sin(a);
     98       
     99        return new Line2D.Double( //
     100            l.getX1() + dx, //
     101            l.getY1() + dy, //
     102            l.getX2() + dx, //
     103            l.getY2() + dy //
     104        );
     105    }
     106   
     107    public static double angle(Line2D l) {
     108        return angle(l.getP1(), l.getP2());
     109    }
     110   
     111    public static double minAngleDiff(double a1, double a2) {
     112        final double d = normalize(a2 - a1);
     113       
     114        return d > Math.PI ? -(2 * Math.PI - d) : d;
     115    }
     116   
     117    public static final Point2D middle(Point2D a, Point2D b) {
     118        return relativePoint(a, a.distance(b) / 2, angle(a, b));
     119    }
     120   
     121    public static final Point2D middle(Line2D l) {
     122        return middle(l.getP1(), l.getP2());
     123    }
     124   
     125    public static Line2D line(Point2D p, double a) {
     126        return new Line2D.Double(p, relativePoint(p, 1, a));
     127    }
     128   
     129    public static Point2D loc(Node node) {
     130        final EastNorth loc = Main.getProjection().latlon2eastNorth(node.getCoor());
     131        return new Point2D.Double(loc.getX(), -loc.getY());
     132    }
     133   
     134    public static List<Point2D> locs(Iterable<Junction> junctions) {
     135        final List<Point2D> locs = new ArrayList<Point2D>();
     136       
     137        for (Junction j : junctions) {
     138            locs.add(loc(j.getNode()));
     139        }
     140       
     141        return locs;
     142    }
     143   
     144    static void area(Path2D area, Path inner, Path outer) {
     145        area.append(inner.getIterator(), false);
     146        area.append(ReversePathIterator.reverse(outer.getIterator()), true);
     147        area.closePath();
     148    }
    149149}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/InteractiveElement.java

    r25783 r26154  
    55
    66abstract class InteractiveElement {
    7         interface Type {
    8                 Type INCOMING_CONNECTOR = new Type() {};
    9                 Type OUTGOING_CONNECTOR = new Type() {};
    10                 Type TURN_CONNECTION = new Type() {};
    11                 Type LANE_ADDER = new Type() {};
    12                 Type EXTENDER = new Type() {};
    13                 Type VIA_CONNECTOR = new Type() {};
    14         }
    15        
    16         public void paintBackground(Graphics2D g2d, State state) {}
    17        
    18         abstract void paint(Graphics2D g2d, State state);
    19        
    20         abstract boolean contains(Point2D p, State state);
    21        
    22         abstract Type getType();
    23        
    24         State activate(State old) {
    25                 return old;
    26         }
    27        
    28         boolean beginDrag(double x, double y) {
    29                 return false;
    30         }
    31        
    32         State drag(double x, double y, InteractiveElement target, State old) {
    33                 return old;
    34         }
    35        
    36         State drop(double x, double y, InteractiveElement target, State old) {
    37                 return old;
    38         }
    39        
    40         abstract int getZIndex();
    41        
    42         State click(State old) {
    43                 return old;
    44         }
     7    interface Type {
     8        Type INCOMING_CONNECTOR = new Type() {};
     9        Type OUTGOING_CONNECTOR = new Type() {};
     10        Type TURN_CONNECTION = new Type() {};
     11        Type LANE_ADDER = new Type() {};
     12        Type EXTENDER = new Type() {};
     13        Type VIA_CONNECTOR = new Type() {};
     14    }
     15   
     16    public void paintBackground(Graphics2D g2d, State state) {}
     17   
     18    abstract void paint(Graphics2D g2d, State state);
     19   
     20    abstract boolean contains(Point2D p, State state);
     21   
     22    abstract Type getType();
     23   
     24    State activate(State old) {
     25        return old;
     26    }
     27   
     28    boolean beginDrag(double x, double y) {
     29        return false;
     30    }
     31   
     32    State drag(double x, double y, InteractiveElement target, State old) {
     33        return old;
     34    }
     35   
     36    State drop(double x, double y, InteractiveElement target, State old) {
     37        return old;
     38    }
     39   
     40    abstract int getZIndex();
     41   
     42    State click(State old) {
     43        return old;
     44    }
    4545}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionGui.java

    r25783 r26154  
    3737
    3838class JunctionGui {
    39         private final class TurnConnection extends InteractiveElement {
    40                 private final Turn turn;
    41                
    42                 private Point2D dragBegin;
    43                 private double dragOffsetX = 0;
    44                 private double dragOffsetY = 0;
    45                
    46                 public TurnConnection(Turn turn) {
    47                         this.turn = turn;
    48                 }
    49                
    50                 @Override
    51                 void paint(Graphics2D g2d, State state) {
    52                         if (isVisible(state)) {
    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                                 }
    79                                
    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;
    87                 }
    88                
    89                 private boolean isVisible(State state) {
    90                         if (state instanceof State.AllTurns) {
    91                                 return true;
    92                         } else if (state instanceof State.OutgoingActive) {
    93                                 return turn.getFrom().equals(((State.OutgoingActive) state).getLane().getModel());
    94                         } else if (state instanceof State.IncomingActive) {
    95                                 return turn.getTo().equals(((State.IncomingActive) state).getRoadEnd());
    96                         }
    97                        
    98                         return false;
    99                 }
    100                
    101                 @Override
    102                 boolean contains(Point2D p, State state) {
    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;
    127                 }
    128                
    129                 private double strokeWidth() {
    130                         final BasicStroke stroke = (BasicStroke) getContainer().getConnectionStroke();
    131                         return stroke.getLineWidth();
    132                 }
    133                
    134                 @Override
    135                 Type getType() {
    136                         return Type.TURN_CONNECTION;
    137                 }
    138                
    139                 @Override
    140                 int getZIndex() {
    141                         return 0;
    142                 }
    143                
    144                 @Override
    145                 boolean beginDrag(double x, double y) {
    146                         dragBegin = new Point2D.Double(x, y);
    147                         dragOffsetX = 0;
    148                         dragOffsetY = 0;
    149                         return true;
    150                 }
    151                
    152                 @Override
    153                 State drag(double x, double y, InteractiveElement target, State old) {
    154                         dragOffsetX = x - dragBegin.getX();
    155                         dragOffsetY = y - dragBegin.getY();
    156                         return old;
    157                 }
    158                
    159                 @Override
    160                 State drop(double x, double y, InteractiveElement target, State old) {
    161                         drag(x, y, target, old);
    162                        
    163                         if (isRemoveDragOffset()) {
    164                                 turn.remove();
    165                         }
    166                        
    167                         dragBegin = null;
    168                         dragOffsetX = 0;
    169                         dragOffsetY = 0;
    170                         return new State.Dirty(old);
    171                 }
    172                
    173                 private boolean isRemoveDragOffset() {
    174                         final double r = getContainer().getGui(turn.getFrom().getRoad()).connectorRadius;
    175                         final double max = r - strokeWidth() / 2;
    176                         return hypot(dragOffsetX, dragOffsetY) > max;
    177                 }
    178         }
    179        
    180         private final class Corner {
    181                 final double x1;
    182                 final double y1;
    183                
    184                 final double cx1;
    185                 final double cy1;
    186                
    187                 final double cx2;
    188                 final double cy2;
    189                
    190                 final double x2;
    191                 final double y2;
    192                
    193                 public Corner(Point2D c1, Point2D cp1, Point2D cp2, Point2D c2) {
    194                         this.x1 = c1.getX();
    195                         this.y1 = c1.getY();
    196                         this.cx1 = cp1.getX();
    197                         this.cy1 = cp1.getY();
    198                         this.cx2 = cp2.getX();
    199                         this.cy2 = cp2.getY();
    200                         this.x2 = c2.getX();
    201                         this.y2 = c2.getY();
    202                 }
    203                
    204                 @Override
    205                 public String toString() {
    206                         return "Corner [x1=" + x1 + ", y1=" + y1 + ", cx1=" + cx1 + ", cy1=" + cy1 + ", cx2=" + cx2 + ", cy2=" + cy2
    207                             + ", x2=" + x2 + ", y2=" + y2 + "]";
    208                 }
    209         }
    210        
    211         private final class Linkage implements Comparable<Linkage> {
    212                 final RoadGui roadGui;
    213                 final Road.End roadEnd;
    214                 final double angle;
    215                
    216                 double lTrim;
    217                 double rTrim;
    218                
    219                 public Linkage(Road.End roadEnd) {
    220                         this.roadGui = getContainer().getGui(roadEnd.getRoad());
    221                         this.roadEnd = roadEnd;
    222                         this.angle = normalize(roadGui.getAngle(roadEnd) + PI);
    223                        
    224                         roads.put(angle, this);
    225                 }
    226                
    227                 @Override
    228                 public int compareTo(Linkage o) {
    229                         return Double.compare(angle, o.angle);
    230                 }
    231                
    232                 public void trimLeft(Linkage right) {
    233                         right.trimRight(this);
    234                        
    235                         final Line2D leftCurb = roadGui.getLeftCurb(roadEnd);
    236                         final Line2D rightCurb = right.roadGui.getRightCurb(right.roadEnd);
    237                        
    238                         final double leftAngle = angle(leftCurb);
    239                         final double rightAngle = angle(rightCurb);
    240                        
    241                         final Point2D isect;
    242                         if (abs(PI - normalize(rightAngle - leftAngle)) > PI / 12) {
    243                                 isect = intersection(leftCurb, rightCurb);
    244                         } else {
    245                                 isect = GuiUtil.relativePoint(leftCurb.getP1(), roadGui.getWidth(roadEnd) / 2, angle);
    246                         }
    247                        
    248                         if (Math.abs(leftAngle - angle(leftCurb.getP1(), isect)) < 0.1) {
    249                                 lTrim = leftCurb.getP1().distance(isect);
    250                         }
    251                 }
    252                
    253                 private void trimRight(Linkage left) {
    254                         final Line2D rightCurb = roadGui.getRightCurb(roadEnd);
    255                         final Line2D leftCurb = left.roadGui.getLeftCurb(left.roadEnd);
    256                        
    257                         final double rightAngle = angle(rightCurb);
    258                         final double leftAngle = angle(leftCurb);
    259                        
    260                         final Point2D isect;
    261                         if (abs(PI - normalize(rightAngle - leftAngle)) > PI / 12) {
    262                                 isect = intersection(rightCurb, leftCurb);
    263                         } else {
    264                                 isect = GuiUtil.relativePoint(rightCurb.getP1(), roadGui.getWidth(roadEnd) / 2, angle);
    265                         }
    266                        
    267                         if (Math.abs(rightAngle - angle(rightCurb.getP1(), isect)) < 0.1) {
    268                                 rTrim = rightCurb.getP1().distance(isect);
    269                         }
    270                 }
    271                
    272                 public void trimAdjust() {
    273                         final double MAX_TAN = tan(PI / 2 - MAX_ANGLE);
    274                        
    275                         final double sin = roadGui.getWidth(roadEnd);
    276                         final double cos = abs(lTrim - rTrim);
    277                         final double tan = sin / cos;
    278                        
    279                         if (tan < MAX_TAN) {
    280                                 lTrim = max(lTrim, rTrim - sin / MAX_TAN);
    281                                 rTrim = max(rTrim, lTrim - sin / MAX_TAN);
    282                         }
    283                        
    284                         lTrim += container.getLaneWidth() / 2;
    285                         rTrim += container.getLaneWidth() / 2;
    286                 }
    287         }
    288        
    289         // max angle between corners
    290         private static final double MAX_ANGLE = Math.toRadians(30);
    291        
    292         private final GuiContainer container;
    293         private final Junction junction;
    294        
    295         final double x;
    296         final double y;
    297        
    298         private final NavigableMap<Double, Linkage> roads = new TreeMap<Double, Linkage>();
    299        
    300         private final Path2D area = new Path2D.Double();
    301        
    302         public JunctionGui(GuiContainer container, Junction j) {
    303                 this.container = container;
    304                 this.junction = j;
    305                
    306                 container.register(this);
    307                
    308                 final Point2D loc = container.translateAndScale(loc(j.getNode()));
    309                 this.x = loc.getX();
    310                 this.y = loc.getY();
    311                
    312                 final Set<Road> done = new HashSet<Road>();
    313                 for (Road r : j.getRoads()) {
    314                         if (!done.contains(r)) {
    315                                 done.add(r);
    316                                
    317                                 if (r.getFromEnd().getJunction().equals(j)) {
    318                                         new Linkage(r.getFromEnd());
    319                                 }
    320                                 if (r.getToEnd().getJunction().equals(j)) {
    321                                         new Linkage(r.getToEnd());
    322                                 }
    323                         }
    324                 }
    325                
    326                 recalculate();
    327         }
    328        
    329         void recalculate() {
    330                 for (Linkage l : roads.values()) {
    331                         l.lTrim = 0;
    332                         l.rTrim = 0;
    333                 }
    334                
    335                 area.reset();
    336                 if (roads.size() < 2) {
    337                         return;
    338                 }
    339                
    340                 Linkage last = roads.lastEntry().getValue();
    341                 for (Linkage l : roads.values()) {
    342                         l.trimLeft(last);
    343                         last = l;
    344                 }
    345                 for (Linkage l : roads.values()) {
    346                         l.trimAdjust();
    347                 }
    348                
    349                 boolean first = true;
    350                 for (Corner c : corners()) {
    351                         if (first) {
    352                                 area.moveTo(c.x1, c.y1);
    353                                 first = false;
    354                         } else {
    355                                 area.lineTo(c.x1, c.y1);
    356                         }
    357                        
    358                         area.curveTo(c.cx1, c.cy1, c.cx2, c.cy2, c.x2, c.y2);
    359                 }
    360                
    361                 area.closePath();
    362         }
    363        
    364         private Iterable<Corner> corners() {
    365                 final List<Corner> result = new ArrayList<JunctionGui.Corner>(roads.size());
    366                
    367                 Linkage last = roads.lastEntry().getValue();
    368                 for (Linkage l : roads.values()) {
    369                         result.add(corner(last, l));
    370                         last = l;
    371                 }
    372                
    373                 return result;
    374         }
    375        
    376         private Corner corner(Linkage right, Linkage left) {
    377                 final Line2D rightCurb = right.roadGui.getRightCurb(right.roadEnd);
    378                 final Line2D leftCurb = left.roadGui.getLeftCurb(left.roadEnd);
    379                
    380                 final double rightAngle = angle(rightCurb);
    381                 final double leftAngle = angle(leftCurb);
    382                
    383                 final double delta = normalize(leftAngle - rightAngle);
    384                
    385                 final boolean wide = delta > PI;
    386                 final double a = wide ? max(0, delta - (PI + 2 * MAX_ANGLE)) : delta;
    387                
    388                 final double cpf1 = cpf(a, container.getLaneWidth() / 2 + (wide ? right.roadGui.getWidth(right.roadEnd) : 0));
    389                 final double cpf2 = cpf(a, container.getLaneWidth() / 2 + (wide ? left.roadGui.getWidth(left.roadEnd) : 0));
    390                
    391                 final Point2D c1 = relativePoint(rightCurb.getP1(), cpf1, right.angle + PI);
    392                 final Point2D c2 = relativePoint(leftCurb.getP1(), cpf2, left.angle + PI);
    393                
    394                 return new Corner(rightCurb.getP1(), c1, c2, leftCurb.getP1());
    395         }
    396        
    397         public Set<RoadGui> getRoads() {
    398                 final Set<RoadGui> result = new HashSet<RoadGui>();
    399                
    400                 for (Linkage l : roads.values()) {
    401                         result.add(l.roadGui);
    402                 }
    403                
    404                 return Collections.unmodifiableSet(result);
    405         }
    406        
    407         double getLeftTrim(Road.End end) {
    408                 return getLinkage(end).lTrim;
    409         }
    410        
    411         private Linkage getLinkage(Road.End end) {
    412                 final double a = normalize(getContainer().getGui(end.getRoad()).getAngle(end) + PI);
    413                 final Map.Entry<Double, Linkage> e = roads.floorEntry(a);
    414                 return e != null ? e.getValue() : null;
    415         }
    416        
    417         double getRightTrim(Road.End end) {
    418                 return getLinkage(end).rTrim;
    419         }
    420        
    421         Point2D getPoint() {
    422                 return new Point2D.Double(x, y);
    423         }
    424        
    425         public GuiContainer getContainer() {
    426                 return container;
    427         }
    428        
    429         public Junction getModel() {
    430                 return junction;
    431         }
    432        
    433         public List<InteractiveElement> paint(Graphics2D g2d) {
    434                 g2d.setColor(new Color(96, 96, 96));
    435                 g2d.fill(area);
    436                
    437                 final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
    438                
    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                         }
    445                 }
    446                
    447                 return result;
    448         }
    449        
    450         public Rectangle2D getBounds() {
    451                 return area.getBounds2D();
    452         }
     39    private final class TurnConnection extends InteractiveElement {
     40        private final Turn turn;
     41       
     42        private Point2D dragBegin;
     43        private double dragOffsetX = 0;
     44        private double dragOffsetY = 0;
     45       
     46        public TurnConnection(Turn turn) {
     47            this.turn = turn;
     48        }
     49       
     50        @Override
     51        void paint(Graphics2D g2d, State state) {
     52            if (isVisible(state)) {
     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                }
     79               
     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;
     87        }
     88       
     89        private boolean isVisible(State state) {
     90            if (state instanceof State.AllTurns) {
     91                return true;
     92            } else if (state instanceof State.OutgoingActive) {
     93                return turn.getFrom().equals(((State.OutgoingActive) state).getLane().getModel());
     94            } else if (state instanceof State.IncomingActive) {
     95                return turn.getTo().equals(((State.IncomingActive) state).getRoadEnd());
     96            }
     97           
     98            return false;
     99        }
     100       
     101        @Override
     102        boolean contains(Point2D p, State state) {
     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;
     127        }
     128       
     129        private double strokeWidth() {
     130            final BasicStroke stroke = (BasicStroke) getContainer().getConnectionStroke();
     131            return stroke.getLineWidth();
     132        }
     133       
     134        @Override
     135        Type getType() {
     136            return Type.TURN_CONNECTION;
     137        }
     138       
     139        @Override
     140        int getZIndex() {
     141            return 0;
     142        }
     143       
     144        @Override
     145        boolean beginDrag(double x, double y) {
     146            dragBegin = new Point2D.Double(x, y);
     147            dragOffsetX = 0;
     148            dragOffsetY = 0;
     149            return true;
     150        }
     151       
     152        @Override
     153        State drag(double x, double y, InteractiveElement target, State old) {
     154            dragOffsetX = x - dragBegin.getX();
     155            dragOffsetY = y - dragBegin.getY();
     156            return old;
     157        }
     158       
     159        @Override
     160        State drop(double x, double y, InteractiveElement target, State old) {
     161            drag(x, y, target, old);
     162           
     163            if (isRemoveDragOffset()) {
     164                turn.remove();
     165            }
     166           
     167            dragBegin = null;
     168            dragOffsetX = 0;
     169            dragOffsetY = 0;
     170            return new State.Dirty(old);
     171        }
     172       
     173        private boolean isRemoveDragOffset() {
     174            final double r = getContainer().getGui(turn.getFrom().getRoad()).connectorRadius;
     175            final double max = r - strokeWidth() / 2;
     176            return hypot(dragOffsetX, dragOffsetY) > max;
     177        }
     178    }
     179   
     180    private final class Corner {
     181        final double x1;
     182        final double y1;
     183       
     184        final double cx1;
     185        final double cy1;
     186       
     187        final double cx2;
     188        final double cy2;
     189       
     190        final double x2;
     191        final double y2;
     192       
     193        public Corner(Point2D c1, Point2D cp1, Point2D cp2, Point2D c2) {
     194            this.x1 = c1.getX();
     195            this.y1 = c1.getY();
     196            this.cx1 = cp1.getX();
     197            this.cy1 = cp1.getY();
     198            this.cx2 = cp2.getX();
     199            this.cy2 = cp2.getY();
     200            this.x2 = c2.getX();
     201            this.y2 = c2.getY();
     202        }
     203       
     204        @Override
     205        public String toString() {
     206            return "Corner [x1=" + x1 + ", y1=" + y1 + ", cx1=" + cx1 + ", cy1=" + cy1 + ", cx2=" + cx2 + ", cy2=" + cy2
     207                + ", x2=" + x2 + ", y2=" + y2 + "]";
     208        }
     209    }
     210   
     211    private final class Linkage implements Comparable<Linkage> {
     212        final RoadGui roadGui;
     213        final Road.End roadEnd;
     214        final double angle;
     215       
     216        double lTrim;
     217        double rTrim;
     218       
     219        public Linkage(Road.End roadEnd) {
     220            this.roadGui = getContainer().getGui(roadEnd.getRoad());
     221            this.roadEnd = roadEnd;
     222            this.angle = normalize(roadGui.getAngle(roadEnd) + PI);
     223           
     224            roads.put(angle, this);
     225        }
     226       
     227        @Override
     228        public int compareTo(Linkage o) {
     229            return Double.compare(angle, o.angle);
     230        }
     231       
     232        public void trimLeft(Linkage right) {
     233            right.trimRight(this);
     234           
     235            final Line2D leftCurb = roadGui.getLeftCurb(roadEnd);
     236            final Line2D rightCurb = right.roadGui.getRightCurb(right.roadEnd);
     237           
     238            final double leftAngle = angle(leftCurb);
     239            final double rightAngle = angle(rightCurb);
     240           
     241            final Point2D isect;
     242            if (abs(PI - normalize(rightAngle - leftAngle)) > PI / 12) {
     243                isect = intersection(leftCurb, rightCurb);
     244            } else {
     245                isect = GuiUtil.relativePoint(leftCurb.getP1(), roadGui.getWidth(roadEnd) / 2, angle);
     246            }
     247           
     248            if (Math.abs(leftAngle - angle(leftCurb.getP1(), isect)) < 0.1) {
     249                lTrim = leftCurb.getP1().distance(isect);
     250            }
     251        }
     252       
     253        private void trimRight(Linkage left) {
     254            final Line2D rightCurb = roadGui.getRightCurb(roadEnd);
     255            final Line2D leftCurb = left.roadGui.getLeftCurb(left.roadEnd);
     256           
     257            final double rightAngle = angle(rightCurb);
     258            final double leftAngle = angle(leftCurb);
     259           
     260            final Point2D isect;
     261            if (abs(PI - normalize(rightAngle - leftAngle)) > PI / 12) {
     262                isect = intersection(rightCurb, leftCurb);
     263            } else {
     264                isect = GuiUtil.relativePoint(rightCurb.getP1(), roadGui.getWidth(roadEnd) / 2, angle);
     265            }
     266           
     267            if (Math.abs(rightAngle - angle(rightCurb.getP1(), isect)) < 0.1) {
     268                rTrim = rightCurb.getP1().distance(isect);
     269            }
     270        }
     271       
     272        public void trimAdjust() {
     273            final double MAX_TAN = tan(PI / 2 - MAX_ANGLE);
     274           
     275            final double sin = roadGui.getWidth(roadEnd);
     276            final double cos = abs(lTrim - rTrim);
     277            final double tan = sin / cos;
     278           
     279            if (tan < MAX_TAN) {
     280                lTrim = max(lTrim, rTrim - sin / MAX_TAN);
     281                rTrim = max(rTrim, lTrim - sin / MAX_TAN);
     282            }
     283           
     284            lTrim += container.getLaneWidth() / 2;
     285            rTrim += container.getLaneWidth() / 2;
     286        }
     287    }
     288   
     289    // max angle between corners
     290    private static final double MAX_ANGLE = Math.toRadians(30);
     291   
     292    private final GuiContainer container;
     293    private final Junction junction;
     294   
     295    final double x;
     296    final double y;
     297   
     298    private final NavigableMap<Double, Linkage> roads = new TreeMap<Double, Linkage>();
     299   
     300    private final Path2D area = new Path2D.Double();
     301   
     302    public JunctionGui(GuiContainer container, Junction j) {
     303        this.container = container;
     304        this.junction = j;
     305       
     306        container.register(this);
     307       
     308        final Point2D loc = container.translateAndScale(loc(j.getNode()));
     309        this.x = loc.getX();
     310        this.y = loc.getY();
     311       
     312        final Set<Road> done = new HashSet<Road>();
     313        for (Road r : j.getRoads()) {
     314            if (!done.contains(r)) {
     315                done.add(r);
     316               
     317                if (r.getFromEnd().getJunction().equals(j)) {
     318                    new Linkage(r.getFromEnd());
     319                }
     320                if (r.getToEnd().getJunction().equals(j)) {
     321                    new Linkage(r.getToEnd());
     322                }
     323            }
     324        }
     325       
     326        recalculate();
     327    }
     328   
     329    void recalculate() {
     330        for (Linkage l : roads.values()) {
     331            l.lTrim = 0;
     332            l.rTrim = 0;
     333        }
     334       
     335        area.reset();
     336        if (roads.size() < 2) {
     337            return;
     338        }
     339       
     340        Linkage last = roads.lastEntry().getValue();
     341        for (Linkage l : roads.values()) {
     342            l.trimLeft(last);
     343            last = l;
     344        }
     345        for (Linkage l : roads.values()) {
     346            l.trimAdjust();
     347        }
     348       
     349        boolean first = true;
     350        for (Corner c : corners()) {
     351            if (first) {
     352                area.moveTo(c.x1, c.y1);
     353                first = false;
     354            } else {
     355                area.lineTo(c.x1, c.y1);
     356            }
     357           
     358            area.curveTo(c.cx1, c.cy1, c.cx2, c.cy2, c.x2, c.y2);
     359        }
     360       
     361        area.closePath();
     362    }
     363   
     364    private Iterable<Corner> corners() {
     365        final List<Corner> result = new ArrayList<JunctionGui.Corner>(roads.size());
     366       
     367        Linkage last = roads.lastEntry().getValue();
     368        for (Linkage l : roads.values()) {
     369            result.add(corner(last, l));
     370            last = l;
     371        }
     372       
     373        return result;
     374    }
     375   
     376    private Corner corner(Linkage right, Linkage left) {
     377        final Line2D rightCurb = right.roadGui.getRightCurb(right.roadEnd);
     378        final Line2D leftCurb = left.roadGui.getLeftCurb(left.roadEnd);
     379       
     380        final double rightAngle = angle(rightCurb);
     381        final double leftAngle = angle(leftCurb);
     382       
     383        final double delta = normalize(leftAngle - rightAngle);
     384       
     385        final boolean wide = delta > PI;
     386        final double a = wide ? max(0, delta - (PI + 2 * MAX_ANGLE)) : delta;
     387       
     388        final double cpf1 = cpf(a, container.getLaneWidth() / 2 + (wide ? right.roadGui.getWidth(right.roadEnd) : 0));
     389        final double cpf2 = cpf(a, container.getLaneWidth() / 2 + (wide ? left.roadGui.getWidth(left.roadEnd) : 0));
     390       
     391        final Point2D c1 = relativePoint(rightCurb.getP1(), cpf1, right.angle + PI);
     392        final Point2D c2 = relativePoint(leftCurb.getP1(), cpf2, left.angle + PI);
     393       
     394        return new Corner(rightCurb.getP1(), c1, c2, leftCurb.getP1());
     395    }
     396   
     397    public Set<RoadGui> getRoads() {
     398        final Set<RoadGui> result = new HashSet<RoadGui>();
     399       
     400        for (Linkage l : roads.values()) {
     401            result.add(l.roadGui);
     402        }
     403       
     404        return Collections.unmodifiableSet(result);
     405    }
     406   
     407    double getLeftTrim(Road.End end) {
     408        return getLinkage(end).lTrim;
     409    }
     410   
     411    private Linkage getLinkage(Road.End end) {
     412        final double a = normalize(getContainer().getGui(end.getRoad()).getAngle(end) + PI);
     413        final Map.Entry<Double, Linkage> e = roads.floorEntry(a);
     414        return e != null ? e.getValue() : null;
     415    }
     416   
     417    double getRightTrim(Road.End end) {
     418        return getLinkage(end).rTrim;
     419    }
     420   
     421    Point2D getPoint() {
     422        return new Point2D.Double(x, y);
     423    }
     424   
     425    public GuiContainer getContainer() {
     426        return container;
     427    }
     428   
     429    public Junction getModel() {
     430        return junction;
     431    }
     432   
     433    public List<InteractiveElement> paint(Graphics2D g2d) {
     434        g2d.setColor(new Color(96, 96, 96));
     435        g2d.fill(area);
     436       
     437        final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
     438       
     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            }
     445        }
     446       
     447        return result;
     448    }
     449   
     450    public Rectangle2D getBounds() {
     451        return area.getBounds2D();
     452    }
    453453}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionPane.java

    r25908 r26154  
    2727
    2828class JunctionPane extends JComponent {
    29         private final class MouseInputProcessor extends MouseAdapter {
    30                 private int originX;
    31                 private int originY;
    32                 private int button;
    33                
    34                 public void mousePressed(MouseEvent e) {
    35                         setFocusable(true);
    36                         button = e.getButton();
    37                        
    38                         if (button == MouseEvent.BUTTON1) {
    39                                 final Point2D mouse = translateMouseCoords(e);
    40                                 for (InteractiveElement ie : interactives()) {
    41                                         if (ie.contains(mouse, state)) {
    42                                                 setState(ie.activate(state));
    43                                                 repaint();
    44                                                 break;
    45                                         }
    46                                 }
    47                         }
    48                        
    49                         originX = e.getX();
    50                         originY = e.getY();
    51                 }
    52                
    53                 @Override
    54                 public void mouseReleased(MouseEvent e) {
    55                         if (dragging != null) {
    56                                 final Point2D mouse = translateMouseCoords(e);
    57                                 setState(dragging.drop(mouse.getX(), mouse.getY(), dropTarget(mouse), state));
    58                         }
    59                        
    60                         dragging = null;
    61                         repaint();
    62                 }
    63                
    64                 private InteractiveElement dropTarget(Point2D mouse) {
    65                         for (InteractiveElement ie : interactives()) {
    66                                 if (ie.contains(mouse, state)) {
    67                                         return ie;
    68                                 }
    69                         }
    70                        
    71                         return null;
    72                 }
    73                
    74                 @Override
    75                 public void mouseClicked(MouseEvent e) {
    76                         if (button == MouseEvent.BUTTON1) {
    77                                 final Point2D mouse = translateMouseCoords(e);
    78                                 for (InteractiveElement ie : interactives()) {
    79                                         if (ie.contains(mouse, state)) {
    80                                                 setState(ie.click(state));
    81                                                 break;
    82                                         }
    83                                 }
    84                         }
    85                 }
    86                
    87                 public void mouseDragged(MouseEvent e) {
    88                         if (button == MouseEvent.BUTTON1) {
    89                                 final Point2D mouse = translateMouseCoords(e);
    90                                
    91                                 if (dragging == null) {
    92                                         final Point2D origin = translateCoords(originX, originY);
    93                                         for (InteractiveElement ie : interactives()) {
    94                                                 if (ie.contains(origin, state)) {
    95                                                         if (ie.beginDrag(origin.getX(), origin.getY())) {
    96                                                                 dragging = ie;
    97                                                         }
    98                                                        
    99                                                         break;
    100                                                 }
    101                                         }
    102                                 }
    103                                
    104                                 if (dragging != null) {
    105                                         setState(dragging.drag(mouse.getX(), mouse.getY(), dropTarget(mouse), state));
    106                                 }
    107                         } else if (button == MouseEvent.BUTTON3) {
    108                                 translate(e.getX() - originX, e.getY() - originY);
    109                                
    110                                 originX = e.getX();
    111                                 originY = e.getY();
    112                         }
    113                 }
    114                
    115                 @Override
    116                 public void mouseWheelMoved(MouseWheelEvent e) {
    117                         scale(e.getX(), e.getY(), Math.pow(0.8, e.getWheelRotation()));
    118                 }
    119                
    120                 private Point2D translateMouseCoords(MouseEvent e) {
    121                         return translateCoords(e.getX(), e.getY());
    122                 }
    123                
    124                 private Point2D translateCoords(int x, int y) {
    125                         final double c = Math.cos(-rotation);
    126                         final double s = Math.sin(-rotation);
    127                        
    128                         final double x2 = -translationX + x / scale;
    129                         final double y2 = -translationY + y / scale;
    130                        
    131                         return new Point2D.Double(x2 * c - y2 * s, x2 * s + y2 * c);
    132                 }
    133         }
    134        
    135         private static final long serialVersionUID = 6917061040674799271L;
    136        
    137         private static final Color TRANSPARENT = new Color(0, 0, 0, 0);
    138        
    139         private final MouseInputProcessor mip = new MouseInputProcessor();
    140        
    141         private GuiContainer container;
    142        
    143         private int width = 0;
    144         private int height = 0;
    145         private double rotation = 0;
    146         private double scale = 10;
    147         private double translationX = 0;
    148         private double translationY = 0;
    149         private boolean dirty = true;
    150         private BufferedImage passive;
    151         private BufferedImage interactive;
    152        
    153         private final NavigableMap<Integer, List<InteractiveElement>> interactives = new TreeMap<Integer, List<InteractiveElement>>();
    154         private State state;
    155         private InteractiveElement dragging;
    156        
    157         public JunctionPane(GuiContainer container) {
    158                 setJunction(container);
    159                
    160                 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), "refresh");
    161                 getActionMap().put("refresh", new AbstractAction() {
    162                         private static final long serialVersionUID = 1L;
    163                        
    164                         @Override
    165                         public void actionPerformed(ActionEvent e) {
    166                                 setState(new State.Invalid(state));
    167                         }
    168                 });
    169                
    170                 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0), "zoomIn");
    171                 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "zoomIn");
    172                 getActionMap().put("zoomIn", new AbstractAction() {
    173                         private static final long serialVersionUID = 1L;
    174                        
    175                         @Override
    176                         public void actionPerformed(ActionEvent e) {
    177                                 scale(Math.pow(0.8, -1));
    178                         }
    179                 });
    180                
    181                 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0), "zoomOut");
    182                 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "zoomOut");
    183                 getActionMap().put("zoomOut", new AbstractAction() {
    184                         private static final long serialVersionUID = 1L;
    185                        
    186                         @Override
    187                         public void actionPerformed(ActionEvent e) {
    188                                 scale(Math.pow(0.8, 1));
    189                         }
    190                 });
    191                
    192                 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "center");
    193                 getActionMap().put("center", new AbstractAction() {
    194                         private static final long serialVersionUID = 1L;
    195                        
    196                         @Override
    197                         public void actionPerformed(ActionEvent e) {
    198                                 center();
    199                         }
    200                 });
    201                
    202                 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK), "toggleAllTurns");
    203                 getActionMap().put("toggleAllTurns", new AbstractAction() {
    204                         private static final long serialVersionUID = 1L;
    205                        
    206                         @Override
    207                         public void actionPerformed(ActionEvent e) {
    208                                 toggleAllTurns();
    209                         }
    210                 });
    211                
    212                 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_L, InputEvent.CTRL_DOWN_MASK), "rotateLeft");
    213                 getActionMap().put("rotateLeft", new AbstractAction() {
    214                         private static final long serialVersionUID = 1L;
    215                        
    216                         @Override
    217                         public void actionPerformed(ActionEvent e) {
    218                                 rotation -= Math.PI / 180;
    219                                 setState(new State.Dirty(state));
    220                         }
    221                 });
    222                
    223                 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK), "rotateRight");
    224                 getActionMap().put("rotateRight", new AbstractAction() {
    225                         private static final long serialVersionUID = 1L;
    226                        
    227                         @Override
    228                         public void actionPerformed(ActionEvent e) {
    229                                 rotation += Math.PI / 180;
    230                                 setState(new State.Dirty(state));
    231                         }
    232                 });
    233         }
    234        
    235         public void setJunction(GuiContainer container) {
    236                 removeMouseListener(mip);
    237                 removeMouseMotionListener(mip);
    238                 removeMouseWheelListener(mip);
    239                 interactives.clear();
    240                 dragging = null;
    241                 this.container = container;
    242                
    243                 if (container == null) {
    244                         this.state = null;
    245                 } else {
    246                         setState(new State.Dirty(new State.Default()));
    247                        
    248                         center();
    249                        
    250                         addMouseListener(mip);
    251                         addMouseMotionListener(mip);
    252                         addMouseWheelListener(mip);
    253                 }
    254         }
    255        
    256         private void center() {
    257                 final Rectangle2D bounds = container.getBounds();
    258                
    259                 rotation = 0;
    260                
    261                 scale = Math.min(getHeight() / 2 / bounds.getHeight(), getWidth() / 2 / bounds.getWidth());
    262                
    263                 translationX = -bounds.getCenterX();
    264                 translationY = -bounds.getCenterY();
    265                
    266                 translate(getWidth() / 2d, getHeight() / 2d);
    267         }
    268        
    269         private void toggleAllTurns() {
    270                 if (state instanceof State.AllTurns) {
    271                         setState(((State.AllTurns) state).unwrap());
    272                 } else {
    273                         setState(new State.AllTurns(state));
    274                 }
    275         }
    276        
    277         private void setState(State state) {
    278                 if (state instanceof State.AllTurns) {
    279                         dirty = true;
    280                         this.state = state;
    281                 } else if (state instanceof State.Invalid) {
    282                         container = container.recalculate();
    283                         dirty = true;
    284                         this.state = new State.Default();
    285                 } else if (state instanceof State.Dirty) {
    286                         dirty = true;
    287                         this.state = ((State.Dirty) state).unwrap();
    288                 } else {
    289                         this.state = state;
    290                 }
    291                
    292                 repaint();
    293         }
    294        
    295         void scale(int x, int y, double scale) {
    296                 this.scale *= scale;
    297                
    298                 final double w = getWidth();
    299                 final double h = getHeight();
    300                
    301                 translationX -= (w * (scale - 1)) / (2 * this.scale);
    302                 translationY -= (h * (scale - 1)) / (2 * this.scale);
    303                
    304                 dirty = true;
    305                 repaint();
    306         }
    307        
    308         void scale(double scale) {
    309                 scale(getWidth() / 2, getHeight() / 2, scale);
    310         }
    311        
    312         void translate(double x, double y) {
    313                 translationX += x / scale;
    314                 translationY += y / scale;
    315                
    316                 dirty = true;
    317                 repaint();
    318         }
    319        
    320         @Override
    321         protected void paintComponent(Graphics g) {
    322                 if (getWidth() != width || getHeight() != height) {
    323                         translate((getWidth() - width) / 2d, (getHeight() - height) / 2d);
    324                         width = getWidth();
    325                         height = getHeight();
    326                        
    327                         // translate already set dirty flag
    328                         passive = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
    329                         interactive = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    330                 }
    331                
    332                 if (container == null) {
    333                         super.paintComponent(g);
    334                         return;
    335                 }
    336                
    337                 if (dirty) {
    338                         paintPassive((Graphics2D) passive.getGraphics());
    339                         dirty = false;
    340                 }
    341                 paintInteractive((Graphics2D) interactive.getGraphics());
    342                
    343                 final Graphics2D g2d = (Graphics2D) g;
    344                
    345                 g2d.drawImage(passive, 0, 0, getWidth(), getHeight(), null);
    346                 g2d.drawImage(interactive, 0, 0, getWidth(), getHeight(), null);
    347         }
    348        
    349         private void paintInteractive(Graphics2D g2d) {
    350                 g2d.setBackground(TRANSPARENT);
    351                 g2d.clearRect(0, 0, getWidth(), getHeight());
    352                 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    353                 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    354                 g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    355                
    356                 g2d.scale(scale, scale);
    357                 g2d.translate(translationX, translationY);
    358                 g2d.rotate(rotation);
    359                
    360                 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.7f));
    361                
    362                 for (Map.Entry<Integer, List<InteractiveElement>> e : interactives.entrySet()) {
    363                         for (InteractiveElement ie : e.getValue()) {
    364                                 ie.paintBackground(g2d, state);
    365                         }
    366                         for (InteractiveElement ie : e.getValue()) {
    367                                 ie.paint(g2d, state);
    368                         }
    369                 }
    370         }
    371        
    372         private List<InteractiveElement> interactives() {
    373                 final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
    374                
    375                 for (List<InteractiveElement> ies : interactives.descendingMap().values()) {
    376                         result.addAll(ies);
    377                 }
    378                
    379                 return result;
    380         }
    381        
    382         private void paintPassive(Graphics2D g2d) {
    383                 interactives.clear();
    384                
    385                 g2d.setBackground(new Color(100, 160, 240));
    386                 g2d.clearRect(0, 0, getWidth(), getHeight());
    387                
    388                 g2d.scale(scale, scale);
    389                 g2d.translate(translationX, translationY);
    390                 g2d.rotate(rotation);
    391                
    392                 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    393                
    394                 g2d.setColor(Color.GRAY);
    395                 for (RoadGui r : container.getRoads()) {
    396                         addAllInteractives(r.paint(g2d));
    397                 }
    398                
    399                 for (JunctionGui j : container.getJunctions()) {
    400                         addAllInteractives(j.paint(g2d));
    401                         dot(g2d, new Point2D.Double(j.x, j.y), container.getLaneWidth() / 5);
    402                 }
    403         }
    404        
    405         private void addAllInteractives(List<InteractiveElement> ies) {
    406                 for (InteractiveElement ie : ies) {
    407                         final List<InteractiveElement> existing = interactives.get(ie.getZIndex());
    408                        
    409                         final List<InteractiveElement> list;
    410                         if (existing == null) {
    411                                 list = new ArrayList<InteractiveElement>();
    412                                 interactives.put(ie.getZIndex(), list);
    413                         } else {
    414                                 list = existing;
    415                         }
    416                        
    417                         list.add(ie);
    418                 }
    419         }
    420        
    421         static void dot(Graphics2D g2d, Point2D p, double r, Color c) {
    422                 final Color old = g2d.getColor();
    423                
    424                 g2d.setColor(c);
    425                 g2d.fill(new Ellipse2D.Double(p.getX() - r, p.getY() - r, 2 * r, 2 * r));
    426                
    427                 g2d.setColor(old);
    428         }
    429        
    430         static void dot(Graphics2D g2d, Point2D p, double r) {
    431                 dot(g2d, p, r, Color.RED);
    432         }
     29    private final class MouseInputProcessor extends MouseAdapter {
     30        private int originX;
     31        private int originY;
     32        private int button;
     33       
     34        public void mousePressed(MouseEvent e) {
     35            setFocusable(true);
     36            button = e.getButton();
     37           
     38            if (button == MouseEvent.BUTTON1) {
     39                final Point2D mouse = translateMouseCoords(e);
     40                for (InteractiveElement ie : interactives()) {
     41                    if (ie.contains(mouse, state)) {
     42                        setState(ie.activate(state));
     43                        repaint();
     44                        break;
     45                    }
     46                }
     47            }
     48           
     49            originX = e.getX();
     50            originY = e.getY();
     51        }
     52       
     53        @Override
     54        public void mouseReleased(MouseEvent e) {
     55            if (dragging != null) {
     56                final Point2D mouse = translateMouseCoords(e);
     57                setState(dragging.drop(mouse.getX(), mouse.getY(), dropTarget(mouse), state));
     58            }
     59           
     60            dragging = null;
     61            repaint();
     62        }
     63       
     64        private InteractiveElement dropTarget(Point2D mouse) {
     65            for (InteractiveElement ie : interactives()) {
     66                if (ie.contains(mouse, state)) {
     67                    return ie;
     68                }
     69            }
     70           
     71            return null;
     72        }
     73       
     74        @Override
     75        public void mouseClicked(MouseEvent e) {
     76            if (button == MouseEvent.BUTTON1) {
     77                final Point2D mouse = translateMouseCoords(e);
     78                for (InteractiveElement ie : interactives()) {
     79                    if (ie.contains(mouse, state)) {
     80                        setState(ie.click(state));
     81                        break;
     82                    }
     83                }
     84            }
     85        }
     86       
     87        public void mouseDragged(MouseEvent e) {
     88            if (button == MouseEvent.BUTTON1) {
     89                final Point2D mouse = translateMouseCoords(e);
     90               
     91                if (dragging == null) {
     92                    final Point2D origin = translateCoords(originX, originY);
     93                    for (InteractiveElement ie : interactives()) {
     94                        if (ie.contains(origin, state)) {
     95                            if (ie.beginDrag(origin.getX(), origin.getY())) {
     96                                dragging = ie;
     97                            }
     98                           
     99                            break;
     100                        }
     101                    }
     102                }
     103               
     104                if (dragging != null) {
     105                    setState(dragging.drag(mouse.getX(), mouse.getY(), dropTarget(mouse), state));
     106                }
     107            } else if (button == MouseEvent.BUTTON3) {
     108                translate(e.getX() - originX, e.getY() - originY);
     109               
     110                originX = e.getX();
     111                originY = e.getY();
     112            }
     113        }
     114       
     115        @Override
     116        public void mouseWheelMoved(MouseWheelEvent e) {
     117            scale(e.getX(), e.getY(), Math.pow(0.8, e.getWheelRotation()));
     118        }
     119       
     120        private Point2D translateMouseCoords(MouseEvent e) {
     121            return translateCoords(e.getX(), e.getY());
     122        }
     123       
     124        private Point2D translateCoords(int x, int y) {
     125            final double c = Math.cos(-rotation);
     126            final double s = Math.sin(-rotation);
     127           
     128            final double x2 = -translationX + x / scale;
     129            final double y2 = -translationY + y / scale;
     130           
     131            return new Point2D.Double(x2 * c - y2 * s, x2 * s + y2 * c);
     132        }
     133    }
     134   
     135    private static final long serialVersionUID = 6917061040674799271L;
     136   
     137    private static final Color TRANSPARENT = new Color(0, 0, 0, 0);
     138   
     139    private final MouseInputProcessor mip = new MouseInputProcessor();
     140   
     141    private GuiContainer container;
     142   
     143    private int width = 0;
     144    private int height = 0;
     145    private double rotation = 0;
     146    private double scale = 10;
     147    private double translationX = 0;
     148    private double translationY = 0;
     149    private boolean dirty = true;
     150    private BufferedImage passive;
     151    private BufferedImage interactive;
     152   
     153    private final NavigableMap<Integer, List<InteractiveElement>> interactives = new TreeMap<Integer, List<InteractiveElement>>();
     154    private State state;
     155    private InteractiveElement dragging;
     156   
     157    public JunctionPane(GuiContainer container) {
     158        setJunction(container);
     159       
     160        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), "refresh");
     161        getActionMap().put("refresh", new AbstractAction() {
     162            private static final long serialVersionUID = 1L;
     163           
     164            @Override
     165            public void actionPerformed(ActionEvent e) {
     166                setState(new State.Invalid(state));
     167            }
     168        });
     169       
     170        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0), "zoomIn");
     171        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "zoomIn");
     172        getActionMap().put("zoomIn", new AbstractAction() {
     173            private static final long serialVersionUID = 1L;
     174           
     175            @Override
     176            public void actionPerformed(ActionEvent e) {
     177                scale(Math.pow(0.8, -1));
     178            }
     179        });
     180       
     181        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0), "zoomOut");
     182        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "zoomOut");
     183        getActionMap().put("zoomOut", new AbstractAction() {
     184            private static final long serialVersionUID = 1L;
     185           
     186            @Override
     187            public void actionPerformed(ActionEvent e) {
     188                scale(Math.pow(0.8, 1));
     189            }
     190        });
     191       
     192        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "center");
     193        getActionMap().put("center", new AbstractAction() {
     194            private static final long serialVersionUID = 1L;
     195           
     196            @Override
     197            public void actionPerformed(ActionEvent e) {
     198                center();
     199            }
     200        });
     201       
     202        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK), "toggleAllTurns");
     203        getActionMap().put("toggleAllTurns", new AbstractAction() {
     204            private static final long serialVersionUID = 1L;
     205           
     206            @Override
     207            public void actionPerformed(ActionEvent e) {
     208                toggleAllTurns();
     209            }
     210        });
     211       
     212        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_L, InputEvent.CTRL_DOWN_MASK), "rotateLeft");
     213        getActionMap().put("rotateLeft", new AbstractAction() {
     214            private static final long serialVersionUID = 1L;
     215           
     216            @Override
     217            public void actionPerformed(ActionEvent e) {
     218                rotation -= Math.PI / 180;
     219                setState(new State.Dirty(state));
     220            }
     221        });
     222       
     223        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK), "rotateRight");
     224        getActionMap().put("rotateRight", new AbstractAction() {
     225            private static final long serialVersionUID = 1L;
     226           
     227            @Override
     228            public void actionPerformed(ActionEvent e) {
     229                rotation += Math.PI / 180;
     230                setState(new State.Dirty(state));
     231            }
     232        });
     233    }
     234   
     235    public void setJunction(GuiContainer container) {
     236        removeMouseListener(mip);
     237        removeMouseMotionListener(mip);
     238        removeMouseWheelListener(mip);
     239        interactives.clear();
     240        dragging = null;
     241        this.container = container;
     242       
     243        if (container == null) {
     244            this.state = null;
     245        } else {
     246            setState(new State.Dirty(new State.Default()));
     247           
     248            center();
     249           
     250            addMouseListener(mip);
     251            addMouseMotionListener(mip);
     252            addMouseWheelListener(mip);
     253        }
     254    }
     255   
     256    private void center() {
     257        final Rectangle2D bounds = container.getBounds();
     258       
     259        rotation = 0;
     260       
     261        scale = Math.min(getHeight() / 2 / bounds.getHeight(), getWidth() / 2 / bounds.getWidth());
     262       
     263        translationX = -bounds.getCenterX();
     264        translationY = -bounds.getCenterY();
     265       
     266        translate(getWidth() / 2d, getHeight() / 2d);
     267    }
     268   
     269    private void toggleAllTurns() {
     270        if (state instanceof State.AllTurns) {
     271            setState(((State.AllTurns) state).unwrap());
     272        } else {
     273            setState(new State.AllTurns(state));
     274        }
     275    }
     276   
     277    private void setState(State state) {
     278        if (state instanceof State.AllTurns) {
     279            dirty = true;
     280            this.state = state;
     281        } else if (state instanceof State.Invalid) {
     282            container = container.recalculate();
     283            dirty = true;
     284            this.state = new State.Default();
     285        } else if (state instanceof State.Dirty) {
     286            dirty = true;
     287            this.state = ((State.Dirty) state).unwrap();
     288        } else {
     289            this.state = state;
     290        }
     291       
     292        repaint();
     293    }
     294   
     295    void scale(int x, int y, double scale) {
     296        this.scale *= scale;
     297       
     298        final double w = getWidth();
     299        final double h = getHeight();
     300       
     301        translationX -= (w * (scale - 1)) / (2 * this.scale);
     302        translationY -= (h * (scale - 1)) / (2 * this.scale);
     303       
     304        dirty = true;
     305        repaint();
     306    }
     307   
     308    void scale(double scale) {
     309        scale(getWidth() / 2, getHeight() / 2, scale);
     310    }
     311   
     312    void translate(double x, double y) {
     313        translationX += x / scale;
     314        translationY += y / scale;
     315       
     316        dirty = true;
     317        repaint();
     318    }
     319   
     320    @Override
     321    protected void paintComponent(Graphics g) {
     322        if (getWidth() != width || getHeight() != height) {
     323            translate((getWidth() - width) / 2d, (getHeight() - height) / 2d);
     324            width = getWidth();
     325            height = getHeight();
     326           
     327            // translate already set dirty flag
     328            passive = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
     329            interactive = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
     330        }
     331       
     332        if (container == null) {
     333            super.paintComponent(g);
     334            return;
     335        }
     336       
     337        if (dirty) {
     338            paintPassive((Graphics2D) passive.getGraphics());
     339            dirty = false;
     340        }
     341        paintInteractive((Graphics2D) interactive.getGraphics());
     342       
     343        final Graphics2D g2d = (Graphics2D) g;
     344       
     345        g2d.drawImage(passive, 0, 0, getWidth(), getHeight(), null);
     346        g2d.drawImage(interactive, 0, 0, getWidth(), getHeight(), null);
     347    }
     348   
     349    private void paintInteractive(Graphics2D g2d) {
     350        g2d.setBackground(TRANSPARENT);
     351        g2d.clearRect(0, 0, getWidth(), getHeight());
     352        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
     353        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
     354        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
     355       
     356        g2d.scale(scale, scale);
     357        g2d.translate(translationX, translationY);
     358        g2d.rotate(rotation);
     359       
     360        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.7f));
     361       
     362        for (Map.Entry<Integer, List<InteractiveElement>> e : interactives.entrySet()) {
     363            for (InteractiveElement ie : e.getValue()) {
     364                ie.paintBackground(g2d, state);
     365            }
     366            for (InteractiveElement ie : e.getValue()) {
     367                ie.paint(g2d, state);
     368            }
     369        }
     370    }
     371   
     372    private List<InteractiveElement> interactives() {
     373        final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
     374       
     375        for (List<InteractiveElement> ies : interactives.descendingMap().values()) {
     376            result.addAll(ies);
     377        }
     378       
     379        return result;
     380    }
     381   
     382    private void paintPassive(Graphics2D g2d) {
     383        interactives.clear();
     384       
     385        g2d.setBackground(new Color(100, 160, 240));
     386        g2d.clearRect(0, 0, getWidth(), getHeight());
     387       
     388        g2d.scale(scale, scale);
     389        g2d.translate(translationX, translationY);
     390        g2d.rotate(rotation);
     391       
     392        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
     393       
     394        g2d.setColor(Color.GRAY);
     395        for (RoadGui r : container.getRoads()) {
     396            addAllInteractives(r.paint(g2d));
     397        }
     398       
     399        for (JunctionGui j : container.getJunctions()) {
     400            addAllInteractives(j.paint(g2d));
     401            dot(g2d, new Point2D.Double(j.x, j.y), container.getLaneWidth() / 5);
     402        }
     403    }
     404   
     405    private void addAllInteractives(List<InteractiveElement> ies) {
     406        for (InteractiveElement ie : ies) {
     407            final List<InteractiveElement> existing = interactives.get(ie.getZIndex());
     408           
     409            final List<InteractiveElement> list;
     410            if (existing == null) {
     411                list = new ArrayList<InteractiveElement>();
     412                interactives.put(ie.getZIndex(), list);
     413            } else {
     414                list = existing;
     415            }
     416           
     417            list.add(ie);
     418        }
     419    }
     420   
     421    static void dot(Graphics2D g2d, Point2D p, double r, Color c) {
     422        final Color old = g2d.getColor();
     423       
     424        g2d.setColor(c);
     425        g2d.fill(new Ellipse2D.Double(p.getX() - r, p.getY() - r, 2 * r, 2 * r));
     426       
     427        g2d.setColor(old);
     428    }
     429   
     430    static void dot(Graphics2D g2d, Point2D p, double r) {
     431        dot(g2d, p, r, Color.RED);
     432    }
    433433}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/LaneGui.java

    r25783 r26154  
    2525
    2626final class LaneGui {
    27         final class LengthSlider extends InteractiveElement {
    28                 private final Point2D center = new Point2D.Double();
    29                 private final Ellipse2D circle = new Ellipse2D.Double();
    30                
    31                 private Point2D dragDelta;
    32                
    33                 private LengthSlider() {}
    34                
    35                 @Override
    36                 public void paint(Graphics2D g2d, State state) {
    37                         if (isVisible(state)) {
    38                                 g2d.setColor(Color.BLUE);
    39                                 g2d.fill(circle);
    40                                
    41                                 final String len = METER_FORMAT.format(getModel().getLength());
    42                                 final Rectangle2D bounds = circle.getBounds2D();
    43                                 g2d.setFont(g2d.getFont().deriveFont(Font.BOLD, (float) bounds.getHeight()));
    44                                 g2d.drawString(len, (float) bounds.getMaxX(), (float) bounds.getMaxY());
    45                         }
    46                 }
    47                
    48                 private boolean isVisible(State state) {
    49                         if (state instanceof State.OutgoingActive) {
    50                                 return LaneGui.this.equals(((State.OutgoingActive) state).getLane());
    51                         }
    52                        
    53                         return false;
    54                 }
    55                
    56                 @Override
    57                 public boolean contains(Point2D p, State state) {
    58                         return isVisible(state) && circle.contains(p);
    59                 }
    60                
    61                 @Override
    62                 public Type getType() {
    63                         return Type.INCOMING_CONNECTOR;
    64                 }
    65                
    66                 @Override
    67                 boolean beginDrag(double x, double y) {
    68                         dragDelta = new Point2D.Double(center.getX() - x, center.getY() - y);
    69                         return true;
    70                 }
    71                
    72                 @Override
    73                 State drag(double x, double y, InteractiveElement target, State old) {
    74                         move(x + dragDelta.getX(), y + dragDelta.getY());
    75                         return new State.Dirty(old);
    76                 }
    77                
    78                 void move(double x, double y) {
    79                         final double r = getRoad().connectorRadius;
    80                        
    81                         final double offset = getRoad().getOffset(x, y);
    82                         final double newLength = getModel().getOutgoingRoadEnd().isFromEnd() ? offset : getRoad().getLength() - offset;
    83                         if (newLength > 0) {
    84                                 getModel().setLength(newLength * getRoad().getContainer().getMpp());
    85                         }
    86                        
    87                         center.setLocation(x, y);
    88                         circle.setFrame(x - r, y - r, 2 * r, 2 * r);
    89                 }
    90                
    91                 public void move(Point2D loc) {
    92                         final double x = loc.getX();
    93                         final double y = loc.getY();
    94                         final double r = getRoad().connectorRadius;
    95                        
    96                         center.setLocation(x, y);
    97                         circle.setFrame(x - r, y - r, 2 * r, 2 * r);
    98                 }
    99                
    100                 @Override
    101                 int getZIndex() {
    102                         return 2;
    103                 }
    104         }
    105        
    106         final class OutgoingConnector extends InteractiveElement {
    107                 private final Point2D center = new Point2D.Double();
    108                 private final Ellipse2D circle = new Ellipse2D.Double();
    109                
    110                 private Point2D dragLocation;
    111                 private IncomingConnector dropTarget;
    112                
    113                 private OutgoingConnector() {}
    114                
    115                 @Override
    116                 public void paintBackground(Graphics2D g2d, State state) {
    117                         if (isActive(state)) {
    118                                 final Composite old = g2d.getComposite();
    119                                 g2d.setComposite(((AlphaComposite) old).derive(0.2f));
    120                                
    121                                 g2d.setColor(new Color(255, 127, 31));
    122                                 LaneGui.this.fill(g2d);
    123                                
    124                                 g2d.setComposite(old);
    125                         }
    126                        
    127                         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                                
    151                                 g2d.setStroke(getContainer().getConnectionStroke());
    152                                 g2d.draw(path);
    153                         }
    154                 }
    155                
    156                 @Override
    157                 public void paint(Graphics2D g2d, State state) {
    158                         if (isVisible(state)) {
    159                                 final Composite old = g2d.getComposite();
    160                                 if (isActive(state)) {
    161                                         g2d.setComposite(((AlphaComposite) old).derive(1f));
    162                                 }
    163                                
    164                                 g2d.setColor(Color.WHITE);
    165                                 g2d.fill(circle);
    166                                 g2d.setComposite(old);
    167                         }
    168                 }
    169                
    170                 private boolean isActive(State state) {
    171                         return state instanceof State.OutgoingActive && LaneGui.this.equals(((State.OutgoingActive) state).getLane());
    172                 }
    173                
    174                 private boolean isVisible(State state) {
    175                         if (state instanceof State.Connecting) {
    176                                 return ((State.Connecting) state).getLane().equals(getModel());
    177                         }
    178                        
    179                         return !getRoad().getModel().isPrimary() && getModel().getOutgoingJunction().isPrimary();
    180                 }
    181                
    182                 @Override
    183                 public boolean contains(Point2D p, State state) {
    184                         return isVisible(state) && (circle.contains(p) || LaneGui.this.contains(p));
    185                 }
    186                
    187                 @Override
    188                 public Type getType() {
    189                         return Type.OUTGOING_CONNECTOR;
    190                 }
    191                
    192                 @Override
    193                 public State activate(State old) {
    194                         return new State.OutgoingActive(LaneGui.this);
    195                 }
    196                
    197                 @Override
    198                 boolean beginDrag(double x, double y) {
    199                         return circle.contains(x, y);
    200                 }
    201                
    202                 @Override
    203                 State.Connecting drag(double x, double y, InteractiveElement target, State old) {
    204                         dragLocation = new Point2D.Double(x, y);
    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;
    221                 }
    222                
    223                 @Override
    224                 State drop(double x, double y, InteractiveElement target, State old) {
    225                         final State.Connecting s = drag(x, y, target, old);
    226                         dragLocation = null;
    227                         if (dropTarget == null) {
    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());
    241                         dropTarget = null;
    242                         return new State.Dirty(activate(old));
    243                 }
    244                
    245                 public Point2D getCenter() {
    246                         return (Point2D) center.clone();
    247                 }
    248                
    249                 void move(double x, double y) {
    250                         final double r = getRoad().connectorRadius;
    251                        
    252                         center.setLocation(x, y);
    253                         circle.setFrame(x - r, y - r, 2 * r, 2 * r);
    254                 }
    255                
    256                 @Override
    257                 int getZIndex() {
    258                         return 1;
    259                 }
    260         }
    261        
    262         static final NumberFormat METER_FORMAT = new DecimalFormat("0.0m");
    263        
    264         private final RoadGui road;
    265         private final Lane lane;
    266        
    267         final Path2D area = new Path2D.Double();
    268        
    269         final OutgoingConnector outgoing = new OutgoingConnector();
    270         final LengthSlider lengthSlider;
    271        
    272         private Shape clip;
    273        
    274         public LaneGui(RoadGui road, Lane lane) {
    275                 this.road = road;
    276                 this.lane = lane;
    277                 this.lengthSlider = lane.isExtra() ? new LengthSlider() : null;
    278         }
    279        
    280         public double getLength() {
    281                 return getModel().isExtra() ? lane.getLength() / getRoad().getContainer().getMpp() : getRoad().getLength();
    282         }
    283        
    284         public Lane getModel() {
    285                 return lane;
    286         }
    287        
    288         public RoadGui getRoad() {
    289                 return road;
    290         }
    291        
    292         public GuiContainer getContainer() {
    293                 return getRoad().getContainer();
    294         }
    295        
    296         public Path recalculate(Path inner, Path2D innerLine) {
    297                 area.reset();
    298                
    299                 final double W = getContainer().getLaneWidth();
    300                 final double L = getLength();
    301                
    302                 final double WW = 3 / getContainer().getMpp();
    303                
    304                 final LaneGui left = left();
    305                 final Lane leftModel = left == null ? null : left.getModel();
    306                 final double leftLength = leftModel == null
    307                     || !leftModel.getOutgoingRoadEnd().equals(getModel().getOutgoingRoadEnd()) ? Double.NEGATIVE_INFINITY
    308                     : leftModel.getKind() == Lane.Kind.EXTRA_LEFT ? left.getLength() : L;
    309                
    310                 final Path outer;
    311                 if (getModel().getKind() == Lane.Kind.EXTRA_LEFT) {
    312                         final double AL = 30 / getContainer().getMpp();
    313                         final double SL = max(L, leftLength + AL);
    314                        
    315                         outer = inner.offset(W, SL, SL + AL, 0);
    316                         area(area, inner.subpath(0, L), outer.subpath(0, L + WW));
    317                        
    318                         lengthSlider.move(inner.getPoint(L));
    319                        
    320                         if (L > leftLength) {
    321                                 innerLine.append(inner.subpath(max(0, leftLength + WW), L).getIterator(), leftLength >= 0
    322                                     || getModel().getOutgoingRoadEnd().isFromEnd());
    323                                 final Point2D op = outer.getPoint(L + WW);
    324                                 innerLine.lineTo(op.getX(), op.getY());
    325                         }
    326                 } else if (getModel().getKind() == Lane.Kind.EXTRA_RIGHT) {
    327                         outer = inner.offset(W, L, L + WW, 0);
    328                         area(area, inner.subpath(0, L + WW), outer.subpath(0, L));
    329                        
    330                         lengthSlider.move(outer.getPoint(L));
    331                 } else {
    332                         outer = inner.offset(W, -1, -1, W);
    333                         area(area, inner, outer);
    334                        
    335                         if (leftLength < L) {
    336                                 innerLine.append(inner.subpath(max(0, leftLength + WW), L).getIterator(), leftLength >= 0
    337                                     || getModel().getOutgoingRoadEnd().isFromEnd());
    338                         }
    339                 }
    340                
    341                 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;
    348         }
    349        
    350         public void fill(Graphics2D g2d) {
    351                 final Shape old = g2d.getClip();
    352                 g2d.clip(clip);
    353                 g2d.fill(area);
    354                 g2d.setClip(old);
    355         }
    356        
    357         public void setClip(Shape clip) {
    358                 this.clip = clip;
    359         }
    360        
    361         public boolean contains(Point2D p) {
    362                 return area.contains(p) && clip.contains(p);
    363         }
     27    final class LengthSlider extends InteractiveElement {
     28        private final Point2D center = new Point2D.Double();
     29        private final Ellipse2D circle = new Ellipse2D.Double();
     30       
     31        private Point2D dragDelta;
     32       
     33        private LengthSlider() {}
     34       
     35        @Override
     36        public void paint(Graphics2D g2d, State state) {
     37            if (isVisible(state)) {
     38                g2d.setColor(Color.BLUE);
     39                g2d.fill(circle);
     40               
     41                final String len = METER_FORMAT.format(getModel().getLength());
     42                final Rectangle2D bounds = circle.getBounds2D();
     43                g2d.setFont(g2d.getFont().deriveFont(Font.BOLD, (float) bounds.getHeight()));
     44                g2d.drawString(len, (float) bounds.getMaxX(), (float) bounds.getMaxY());
     45            }
     46        }
     47       
     48        private boolean isVisible(State state) {
     49            if (state instanceof State.OutgoingActive) {
     50                return LaneGui.this.equals(((State.OutgoingActive) state).getLane());
     51            }
     52           
     53            return false;
     54        }
     55       
     56        @Override
     57        public boolean contains(Point2D p, State state) {
     58            return isVisible(state) && circle.contains(p);
     59        }
     60       
     61        @Override
     62        public Type getType() {
     63            return Type.INCOMING_CONNECTOR;
     64        }
     65       
     66        @Override
     67        boolean beginDrag(double x, double y) {
     68            dragDelta = new Point2D.Double(center.getX() - x, center.getY() - y);
     69            return true;
     70        }
     71       
     72        @Override
     73        State drag(double x, double y, InteractiveElement target, State old) {
     74            move(x + dragDelta.getX(), y + dragDelta.getY());
     75            return new State.Dirty(old);
     76        }
     77       
     78        void move(double x, double y) {
     79            final double r = getRoad().connectorRadius;
     80           
     81            final double offset = getRoad().getOffset(x, y);
     82            final double newLength = getModel().getOutgoingRoadEnd().isFromEnd() ? offset : getRoad().getLength() - offset;
     83            if (newLength > 0) {
     84                getModel().setLength(newLength * getRoad().getContainer().getMpp());
     85            }
     86           
     87            center.setLocation(x, y);
     88            circle.setFrame(x - r, y - r, 2 * r, 2 * r);
     89        }
     90       
     91        public void move(Point2D loc) {
     92            final double x = loc.getX();
     93            final double y = loc.getY();
     94            final double r = getRoad().connectorRadius;
     95           
     96            center.setLocation(x, y);
     97            circle.setFrame(x - r, y - r, 2 * r, 2 * r);
     98        }
     99       
     100        @Override
     101        int getZIndex() {
     102            return 2;
     103        }
     104    }
     105   
     106    final class OutgoingConnector extends InteractiveElement {
     107        private final Point2D center = new Point2D.Double();
     108        private final Ellipse2D circle = new Ellipse2D.Double();
     109       
     110        private Point2D dragLocation;
     111        private IncomingConnector dropTarget;
     112       
     113        private OutgoingConnector() {}
     114       
     115        @Override
     116        public void paintBackground(Graphics2D g2d, State state) {
     117            if (isActive(state)) {
     118                final Composite old = g2d.getComposite();
     119                g2d.setComposite(((AlphaComposite) old).derive(0.2f));
     120               
     121                g2d.setColor(new Color(255, 127, 31));
     122                LaneGui.this.fill(g2d);
     123               
     124                g2d.setComposite(old);
     125            }
     126           
     127            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               
     151                g2d.setStroke(getContainer().getConnectionStroke());
     152                g2d.draw(path);
     153            }
     154        }
     155       
     156        @Override
     157        public void paint(Graphics2D g2d, State state) {
     158            if (isVisible(state)) {
     159                final Composite old = g2d.getComposite();
     160                if (isActive(state)) {
     161                    g2d.setComposite(((AlphaComposite) old).derive(1f));
     162                }
     163               
     164                g2d.setColor(Color.WHITE);
     165                g2d.fill(circle);
     166                g2d.setComposite(old);
     167            }
     168        }
     169       
     170        private boolean isActive(State state) {
     171            return state instanceof State.OutgoingActive && LaneGui.this.equals(((State.OutgoingActive) state).getLane());
     172        }
     173       
     174        private boolean isVisible(State state) {
     175            if (state instanceof State.Connecting) {
     176                return ((State.Connecting) state).getLane().equals(getModel());
     177            }
     178           
     179            return !getRoad().getModel().isPrimary() && getModel().getOutgoingJunction().isPrimary();
     180        }
     181       
     182        @Override
     183        public boolean contains(Point2D p, State state) {
     184            return isVisible(state) && (circle.contains(p) || LaneGui.this.contains(p));
     185        }
     186       
     187        @Override
     188        public Type getType() {
     189            return Type.OUTGOING_CONNECTOR;
     190        }
     191       
     192        @Override
     193        public State activate(State old) {
     194            return new State.OutgoingActive(LaneGui.this);
     195        }
     196       
     197        @Override
     198        boolean beginDrag(double x, double y) {
     199            return circle.contains(x, y);
     200        }
     201       
     202        @Override
     203        State.Connecting drag(double x, double y, InteractiveElement target, State old) {
     204            dragLocation = new Point2D.Double(x, y);
     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;
     221        }
     222       
     223        @Override
     224        State drop(double x, double y, InteractiveElement target, State old) {
     225            final State.Connecting s = drag(x, y, target, old);
     226            dragLocation = null;
     227            if (dropTarget == null) {
     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());
     241            dropTarget = null;
     242            return new State.Dirty(activate(old));
     243        }
     244       
     245        public Point2D getCenter() {
     246            return (Point2D) center.clone();
     247        }
     248       
     249        void move(double x, double y) {
     250            final double r = getRoad().connectorRadius;
     251           
     252            center.setLocation(x, y);
     253            circle.setFrame(x - r, y - r, 2 * r, 2 * r);
     254        }
     255       
     256        @Override
     257        int getZIndex() {
     258            return 1;
     259        }
     260    }
     261   
     262    static final NumberFormat METER_FORMAT = new DecimalFormat("0.0m");
     263   
     264    private final RoadGui road;
     265    private final Lane lane;
     266   
     267    final Path2D area = new Path2D.Double();
     268   
     269    final OutgoingConnector outgoing = new OutgoingConnector();
     270    final LengthSlider lengthSlider;
     271   
     272    private Shape clip;
     273   
     274    public LaneGui(RoadGui road, Lane lane) {
     275        this.road = road;
     276        this.lane = lane;
     277        this.lengthSlider = lane.isExtra() ? new LengthSlider() : null;
     278    }
     279   
     280    public double getLength() {
     281        return getModel().isExtra() ? lane.getLength() / getRoad().getContainer().getMpp() : getRoad().getLength();
     282    }
     283   
     284    public Lane getModel() {
     285        return lane;
     286    }
     287   
     288    public RoadGui getRoad() {
     289        return road;
     290    }
     291   
     292    public GuiContainer getContainer() {
     293        return getRoad().getContainer();
     294    }
     295   
     296    public Path recalculate(Path inner, Path2D innerLine) {
     297        area.reset();
     298       
     299        final double W = getContainer().getLaneWidth();
     300        final double L = getLength();
     301       
     302        final double WW = 3 / getContainer().getMpp();
     303       
     304        final LaneGui left = left();
     305        final Lane leftModel = left == null ? null : left.getModel();
     306        final double leftLength = leftModel == null
     307            || !leftModel.getOutgoingRoadEnd().equals(getModel().getOutgoingRoadEnd()) ? Double.NEGATIVE_INFINITY
     308            : leftModel.getKind() == Lane.Kind.EXTRA_LEFT ? left.getLength() : L;
     309       
     310        final Path outer;
     311        if (getModel().getKind() == Lane.Kind.EXTRA_LEFT) {
     312            final double AL = 30 / getContainer().getMpp();
     313            final double SL = max(L, leftLength + AL);
     314           
     315            outer = inner.offset(W, SL, SL + AL, 0);
     316            area(area, inner.subpath(0, L), outer.subpath(0, L + WW));
     317           
     318            lengthSlider.move(inner.getPoint(L));
     319           
     320            if (L > leftLength) {
     321                innerLine.append(inner.subpath(max(0, leftLength + WW), L).getIterator(), leftLength >= 0
     322                    || getModel().getOutgoingRoadEnd().isFromEnd());
     323                final Point2D op = outer.getPoint(L + WW);
     324                innerLine.lineTo(op.getX(), op.getY());
     325            }
     326        } else if (getModel().getKind() == Lane.Kind.EXTRA_RIGHT) {
     327            outer = inner.offset(W, L, L + WW, 0);
     328            area(area, inner.subpath(0, L + WW), outer.subpath(0, L));
     329           
     330            lengthSlider.move(outer.getPoint(L));
     331        } else {
     332            outer = inner.offset(W, -1, -1, W);
     333            area(area, inner, outer);
     334           
     335            if (leftLength < L) {
     336                innerLine.append(inner.subpath(max(0, leftLength + WW), L).getIterator(), leftLength >= 0
     337                    || getModel().getOutgoingRoadEnd().isFromEnd());
     338            }
     339        }
     340       
     341        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;
     348    }
     349   
     350    public void fill(Graphics2D g2d) {
     351        final Shape old = g2d.getClip();
     352        g2d.clip(clip);
     353        g2d.fill(area);
     354        g2d.setClip(old);
     355    }
     356   
     357    public void setClip(Shape clip) {
     358        this.clip = clip;
     359    }
     360   
     361    public boolean contains(Point2D p) {
     362        return area.contains(p) && clip.contains(p);
     363    }
    364364}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/Path.java

    r25606 r26154  
    2626 */
    2727abstract class Path {
    28         private static final class SimplePathIterator implements PathIterator {
    29                 private final SimplePathIterator previous;
    30                
    31                 private final int type;
    32                 private final double[] coords;
    33                
    34                 private boolean done = false;
    35                
    36                 public SimplePathIterator(SimplePathIterator previous, int type, double... coords) {
    37                         this.previous = previous;
    38                         this.type = type;
    39                         this.coords = coords;
    40                 }
    41                
    42                 public SimplePathIterator(int type, double... coords) {
    43                         this(null, type, coords);
    44                 }
    45                
    46                 @Override
    47                 public int getWindingRule() {
    48                         return WIND_NON_ZERO;
    49                 }
    50                
    51                 @Override
    52                 public boolean isDone() {
    53                         return done;
    54                 }
    55                
    56                 @Override
    57                 public void next() {
    58                         if (previous != null && !previous.isDone()) {
    59                                 previous.next();
    60                         } else {
    61                                 done = true;
    62                         }
    63                 }
    64                
    65                 @Override
    66                 public int currentSegment(float[] coords) {
    67                         if (previous != null && !previous.isDone()) {
    68                                 return previous.currentSegment(coords);
    69                         } else if (done) {
    70                                 throw new NoSuchElementException("Iterator is already done.");
    71                         }
    72                        
    73                         for (int i = 0; i < 6; ++i) {
    74                                 coords[i] = (float) this.coords[i];
    75                         }
    76                        
    77                         return type;
    78                 }
    79                
    80                 @Override
    81                 public int currentSegment(double[] coords) {
    82                         if (previous != null && !previous.isDone()) {
    83                                 return previous.currentSegment(coords);
    84                         } else if (done) {
    85                                 throw new NoSuchElementException("Iterator is already done.");
    86                         }
    87                        
    88                         for (int i = 0; i < 6; ++i) {
    89                                 coords[i] = this.coords[i];
    90                         }
    91                        
    92                         return type;
    93                 }
    94                
    95         }
    96        
    97         private static final class Line extends Path {
    98                 private final Path previous;
    99                
    100                 private final double endX;
    101                 private final double endY;
    102                
    103                 private final double angle;
    104                
    105                 private final double length;
    106                
    107                 public Line(Path previous, double x, double y, double length) {
    108                         this.previous = previous;
    109                        
    110                         this.endX = x;
    111                         this.endY = y;
    112                        
    113                         this.angle = angle(previous.getEnd(), getEnd());
    114                        
    115                         this.length = length;
    116                 }
    117                
    118                 @Override
    119                 public Point2D getStart() {
    120                         return previous.getStart();
    121                 }
    122                
    123                 @Override
    124                 public Point2D getEnd() {
    125                         return new Point2D.Double(endX, endY);
    126                 }
    127                
    128                 @Override
    129                 public double getEndAngle() {
    130                         return angle;
    131                 }
    132                
    133                 @Override
    134                 public double getLength() {
    135                         return previous.getLength() + length;
    136                 }
    137                
    138                 @Override
    139                 public Path offset(double ws, double m1, double m2, double we) {
    140                         return offsetInternal(ws, m1, m2, we, angle);
    141                 }
    142                
    143                 @Override
    144                 Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
    145                         final double PL = previous.getLength();
    146                         final double ML = PL + length;
    147                        
    148                         final Path prev = previous.offsetInternal(ws, m1, m2, we, angle);
    149                        
    150                         final double wStart = PL <= m1 ? ws : m2 <= PL ? we : ws + (PL - m1) * (we - ws) / (m2 - m1);
    151                         final Point2D from = prev.getEnd();
    152                         final Point2D to = offsetEnd(wStart, endAngle);
    153                        
    154                         if (abs(minAngleDiff(angle, angle(from, to))) > PI / 100) {
    155                                 return previous.offsetInternal(ws, m1, m2, we, endAngle);
    156                         }
    157                        
    158                         if (ML <= m1) {
    159                                 return simpleOffset(prev, ws, endAngle);
    160                         } else if (m2 <= PL) {
    161                                 return simpleOffset(prev, we, endAngle);
    162                         }
    163                        
    164                         final double LL = from.distance(to);
    165                        
    166                         final Point2D m1o = PL <= m1 ? relativePoint(prev.getEnd(), LL * (m1 - PL) / length, angle) : null;
    167                         final Point2D m2t = m2 <= ML ? relativePoint(getEnd(), LL * (ML - m2) / length, angle + PI) : null;
    168                         final Point2D m2o = m2t == null ? null : relativePoint(m2t, we, (angle + endAngle - PI) / 2);
    169                        
    170                         if (m1o != null && m2o != null) {
    171                                 final Line l1 = new Line(prev, m1o.getX(), m1o.getY(), m1 - PL);
    172                                 final Line l2 = new Line(l1, m2o.getX(), m2o.getY(), m2 - m1);
    173                                
    174                                 final Point2D end = offsetEnd(we, endAngle);
    175                                
    176                                 return new Line(l2, end.getX(), end.getY(), ML - m2);
    177                         } else if (m1o != null) {
    178                                 final Line l1 = new Line(prev, m1o.getX(), m1o.getY(), m1 - PL);
    179                                
    180                                 final double w = ws + (ML - m1) * (we - ws) / (m2 - m1);
    181                                 final Point2D end = offsetEnd(w, endAngle);
    182                                
    183                                 return new Line(l1, end.getX(), end.getY(), ML - m1);
    184                         } else if (m2o != null) {
    185                                 final Line l2 = new Line(prev, m2o.getX(), m2o.getY(), m2 - PL);
    186                                
    187                                 final Point2D end = offsetEnd(we, endAngle);
    188                                
    189                                 return new Line(l2, end.getX(), end.getY(), ML - m2);
    190                         } else {
    191                                 final double w = ws + (PL - m1 + length) * (we - ws) / (m2 - m1);
    192                                 final Point2D end = offsetEnd(w, endAngle);
    193                                 return new Line(prev, end.getX(), end.getY(), length);
    194                         }
    195                 }
    196                
    197                 private Path simpleOffset(Path prev, double w, double endAngle) {
    198                         final Point2D offset = offsetEnd(w, endAngle);
    199                         return new Line(prev, offset.getX(), offset.getY(), length);
    200                 }
    201                
    202                 private Point2D offsetEnd(double w, double endAngle) {
    203                         final double da2 = minAngleDiff(angle, endAngle) / 2;
    204                         final double hypotenuse = w / cos(da2);
    205                        
    206                         return relativePoint(getEnd(), hypotenuse, angle + PI / 2 + da2);
    207                 }
    208                
    209                 @Override
    210                 public SimplePathIterator getIterator() {
    211                         return new SimplePathIterator(previous.getIteratorInternal(angle), PathIterator.SEG_LINETO, endX, endY, 0, 0, 0,
    212                             0);
    213                 }
    214                
    215                 @Override
    216                 public Path subpath(double from, double to) {
    217                         final double PL = previous.getLength();
    218                         final double ML = PL + length;
    219                        
    220                         if (to < PL) {
    221                                 return previous.subpath(from, to);
    222                         }
    223                        
    224                         final Point2D end = to < ML ? getPoint(to) : new Point2D.Double(endX, endY);
    225                        
    226                         final double EL = min(ML, to);
    227                         if (PL <= from) {
    228                                 final Point2D start = getPoint(from);
    229                                 return new Line(new Start(start.getX(), start.getY(), angle), end.getX(), end.getY(), EL - from);
    230                         } else {
    231                                 return new Line(previous.subpath(from, to), end.getX(), end.getY(), EL - PL);
    232                         }
    233                 }
    234                
    235                 @Override
    236                 public Point2D getPoint(double offset) {
    237                         final double PL = previous.getLength();
    238                         final double ML = PL + length;
    239                        
    240                         if (offset <= ML && offset >= PL) {
    241                                 final double LL = previous.getEnd().distance(getEnd());
    242                                 return relativePoint(getEnd(), LL * (ML - offset) / length, angle + PI);
    243                         } else {
    244                                 return previous.getPoint(offset);
    245                         }
    246                 }
    247                
    248                 @Override
    249                 SimplePathIterator getIteratorInternal(double endAngle) {
    250                         return getIterator();
    251                 }
    252         }
    253        
    254         // TODO curves are still somewhat broken
    255         private static class Curve extends Path {
    256                 private final Path previous;
    257                
    258                 private final double height;
    259                
    260                 private final double centerX;
    261                 private final double centerY;
    262                 private final double centerToFromAngle;
    263                
    264                 private final double endX;
    265                 private final double endY;
    266                
    267                 private final double fromAngle;
    268                 private final double fromRadius;
    269                 private final double toRadius;
    270                 private final double angle;
    271                
    272                 private final double length;
    273                
    274                 private Curve(Path previous, double r1, double r2, double a, double length, double fromAngle) {
    275                         this.previous = previous;
    276                         this.fromAngle = fromAngle;
    277                         this.fromRadius = r1;
    278                         this.toRadius = r2;
    279                         this.angle = a;
    280                         this.length = length;
    281                        
    282                         final Point2D from = previous.getEnd();
    283                         this.centerToFromAngle = fromAngle - signum(a) * PI / 2;
    284                         final Point2D center = relativePoint(from, r1, centerToFromAngle + PI);
    285                        
    286                         final double toAngle = centerToFromAngle + a;
    287                         this.endX = center.getX() + r2 * cos(toAngle);
    288                         this.endY = center.getY() - r2 * sin(toAngle);
    289                        
    290                         this.centerX = center.getX();
    291                         this.centerY = center.getY();
    292                        
    293                         final double y = new Line2D.Double(center, from).ptLineDist(endX, endY);
    294                         this.height = y / sin(angle);
    295                 }
    296                
    297                 public Curve(Path previous, double r1, double r2, double a, double length) {
    298                         this(previous, r1, r2, a, length, previous.getEndAngle());
    299                 }
    300                
    301                 public Point2D getStart() {
    302                         return previous.getStart();
    303                 }
    304                
    305                 @Override
    306                 public Point2D getEnd() {
    307                         return new Point2D.Double(endX, endY);
    308                 }
    309                
    310                 @Override
    311                 public double getEndAngle() {
    312                         return fromAngle + angle;
    313                 }
    314                
    315                 @Override
    316                 public double getLength() {
    317                         return previous.getLength() + length;
    318                 }
    319                
    320                 @Override
    321                 public Path offset(double ws, double m1, double m2, double we) {
    322                         return offsetInternal(ws, m1, m2, we, previous.getEndAngle() + angle);
    323                 }
    324                
    325                 @Override
    326                 Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
    327                         final double PL = previous.getLength();
    328                         final double ML = PL + length;
    329                        
    330                         final Path prev = previous.offsetInternal(ws, m1, m2, we, fromAngle);
    331                        
    332                         if (ML <= m1) {
    333                                 return simpleOffset(prev, ws);
    334                         } else if (m2 <= PL) {
    335                                 return simpleOffset(prev, we);
    336                         }
    337                        
    338                         final double s = signum(angle);
    339                        
    340                         if (PL < m1 && m2 < ML) {
    341                                 final double l1 = m1 - PL;
    342                                 final double a1 = angle(l1);
    343                                 final double r1 = radius(a1) - s * ws;
    344                                
    345                                 final Curve c1 = new Curve(prev, fromRadius - ws, r1, offsetAngle(prev, a1), l1, fromAngle);
    346                                
    347                                 final double l2 = m2 - m1;
    348                                 final double a2 = angle(l2);
    349                                 final double r2 = radius(a2) - s * we;
    350                                
    351                                 final Curve c2 = new Curve(c1, r1, r2, a2 - a1, l2);
    352                                
    353                                 return new Curve(c2, r2, toRadius - s * we, angle - a2, ML - m2);
    354                         } else if (PL < m1) {
    355                                 final double l1 = m1 - PL;
    356                                 final double a1 = angle(l1);
    357                                 final double r1 = radius(a1) - s * ws;
    358                                
    359                                 final Curve c1 = new Curve(prev, fromRadius - s * ws, r1, offsetAngle(prev, a1), l1, fromAngle);
    360                                
    361                                 final double w = ws + (ML - m1) * (we - ws) / (m2 - m1);
    362                                
    363                                 return new Curve(c1, r1, toRadius - s * w, angle - a1, ML - m1);
    364                         } else if (m2 < ML) {
    365                                 final double w = ws + (PL - m1) * (we - ws) / (m2 - m1);
    366                                
    367                                 final double l2 = m2 - PL;
    368                                 final double a2 = angle(l2);
    369                                 final double r2 = radius(a2) - s * we;
    370                                
    371                                 final Curve c2 = new Curve(prev, fromRadius - s * w, r2, offsetAngle(prev, a2), l2, fromAngle);
    372                                
    373                                 return new Curve(c2, r2, toRadius - s * we, angle - a2, ML - m2);
    374                         } else {
    375                                 final double w1 = ws + (PL - m1) * (we - ws) / (m2 - m1);
    376                                 final double w2 = we - (m2 - ML) * (we - ws) / (m2 - m1);
    377                                
    378                                 return new Curve(prev, fromRadius - s * w1, toRadius - s * w2, offsetAngle(prev, angle), length, fromAngle);
    379                         }
    380                 }
    381                
    382                 private double angle(double l) {
    383                         return l * angle / length;
    384                 }
    385                
    386                 private double radius(double a) {
    387                         return hypot(fromRadius * cos(a), height * sin(a));
    388                 }
    389                
    390                 private double offsetAngle(Path prev, double a) {
    391                         return a;// + GuiUtil.normalize(previous.getEndAngle()
    392                         // - prev.getEndAngle());
    393                 }
    394                
    395                 private Path simpleOffset(Path prev, double w) {
    396                         final double s = signum(angle);
    397                         return new Curve(prev, fromRadius - s * w, toRadius - s * w, offsetAngle(prev, angle), length, fromAngle);
    398                 }
    399                
    400                 @Override
    401                 public SimplePathIterator getIterator() {
    402                         return getIteratorInternal(previous.getEndAngle() + angle);
    403                 }
    404                
    405                 @Override
    406                 public Path subpath(double from, double to) {
    407                         final double PL = previous.getLength();
    408                         final double ML = PL + length;
    409                        
    410                         if (to < PL) {
    411                                 return previous.subpath(from, to);
    412                         }
    413                        
    414                         final double toA = to < ML ? angle(to - PL) : angle;
    415                         final double toR = to < ML ? radius(toA) : toRadius;
    416                        
    417                         final double fromA = from > PL ? angle(from - PL) : 0;
    418                         final double fromR = from > PL ? radius(fromA) : fromRadius;
    419                        
    420                         final double a = toA - fromA;
    421                         final double l = min(ML, to) - max(PL, from);
    422                        
    423                         if (from >= PL) {
    424                                 final Point2D start = getPoint(from);
    425                                 final double fa = fromAngle + fromA;
    426                                
    427                                 return new Curve(new Start(start.getX(), start.getY(), fa), fromR, toR, a, l, fa);
    428                         } else {
    429                                 return new Curve(previous.subpath(from, to), fromR, toR, a, l, fromAngle);
    430                         }
    431                 }
    432                
    433                 @Override
    434                 public Point2D getPoint(double offset) {
    435                         final double PL = previous.getLength();
    436                         final double ML = PL + length;
    437                        
    438                         if (offset <= ML && offset >= PL) {
    439                                 final double a = abs(angle(offset - PL));
    440                                 final double w = fromRadius * cos(a);
    441                                 final double h = -height * sin(a);
    442                                
    443                                 final double r = centerToFromAngle; // rotation angle
    444                                 final double x = w * cos(r) + h * sin(r);
    445                                 final double y = -w * sin(r) + h * cos(r);
    446                                
    447                                 return new Point2D.Double(centerX + x, centerY + y);
    448                         } else {
    449                                 return previous.getPoint(offset);
    450                         }
    451                 }
    452                
    453                 @Override
    454                 SimplePathIterator getIteratorInternal(double endAngle) {
    455                         final Point2D cp1 = relativePoint(previous.getEnd(), cpf(angle, fromRadius), previous.getEndAngle());
    456                         final Point2D cp2 = relativePoint(getEnd(), cpf(angle, toRadius), endAngle + PI);
    457                        
    458                         return new SimplePathIterator(previous.getIteratorInternal(getEndAngle()), PathIterator.SEG_CUBICTO, //
    459                             cp1.getX(), cp1.getY(), cp2.getX(), cp2.getY(), endX, endY //
    460                         );
    461                        
    462                 }
    463         }
    464        
    465         private static class Start extends Path {
    466                 private final double x;
    467                 private final double y;
    468                
    469                 private final double endAngle;
    470                
    471                 public Start(double x, double y, double endAngle) {
    472                         this.x = x;
    473                         this.y = y;
    474                         this.endAngle = endAngle;
    475                 }
    476                
    477                 public Start(double x, double y) {
    478                         this(x, y, Double.NaN);
    479                 }
    480                
    481                 public Point2D getStart() {
    482                         return new Point2D.Double(x, y);
    483                 }
    484                
    485                 public Point2D getEnd() {
    486                         return new Point2D.Double(x, y);
    487                 }
    488                
    489                 @Override
    490                 public double getEndAngle() {
    491                         if (Double.isNaN(endAngle)) {
    492                                 throw new UnsupportedOperationException();
    493                         }
    494                        
    495                         return endAngle;
    496                 }
    497                
    498                 @Override
    499                 public double getLength() {
    500                         return 0;
    501                 }
    502                
    503                 @Override
    504                 public Path offset(double ws, double m1, double m2, double we) {
    505                         throw new UnsupportedOperationException();
    506                 }
    507                
    508                 @Override
    509                 Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
    510                         final Point2D offset = relativePoint(getStart(), ws, endAngle + PI / 2);
    511                         return new Start(offset.getX(), offset.getY(), endAngle);
    512                 }
    513                
    514                 @Override
    515                 public SimplePathIterator getIterator() {
    516                         return new SimplePathIterator(PathIterator.SEG_MOVETO, x, y, 0, 0, 0, 0);
    517                 }
    518                
    519                 @Override
    520                 public Path subpath(double from, double to) {
    521                         if (from > to) {
    522                                 throw new IllegalArgumentException("from > to");
    523                         }
    524                         if (from < 0) {
    525                                 throw new IllegalArgumentException("from < 0");
    526                         }
    527                        
    528                         return this;
    529                 }
    530                
    531                 @Override
    532                 public Point2D getPoint(double offset) {
    533                         if (offset == 0) {
    534                                 return getEnd();
    535                         } else {
    536                                 throw new IllegalArgumentException(Double.toString(offset));
    537                         }
    538                 }
    539                
    540                 @Override
    541                 SimplePathIterator getIteratorInternal(double endAngle) {
    542                         return new SimplePathIterator(PathIterator.SEG_MOVETO, x, y, 0, 0, 0, 0);
    543                 }
    544         }
    545        
    546         public static Path create(double x, double y) {
    547                 return new Start(x, y);
    548         }
    549        
    550         public Path lineTo(double x, double y, double length) {
    551                 return new Line(this, x, y, length);
    552         }
    553        
    554         public Path curveTo(double r1, double r2, double a, double length) {
    555                 return new Curve(this, r1, r2, a, length);
    556         }
    557        
    558         public abstract Path offset(double ws, double m1, double m2, double we);
    559        
    560         abstract Path offsetInternal(double ws, double m1, double m2, double we, double endAngle);
    561        
    562         public abstract double getLength();
    563        
    564         public abstract double getEndAngle();
    565        
    566         public abstract Point2D getStart();
    567        
    568         public abstract Point2D getEnd();
    569        
    570         public abstract SimplePathIterator getIterator();
    571        
    572         abstract SimplePathIterator getIteratorInternal(double endAngle);
    573        
    574         public abstract Path subpath(double from, double to);
    575        
    576         public abstract Point2D getPoint(double offset);
     28    private static final class SimplePathIterator implements PathIterator {
     29        private final SimplePathIterator previous;
     30       
     31        private final int type;
     32        private final double[] coords;
     33       
     34        private boolean done = false;
     35       
     36        public SimplePathIterator(SimplePathIterator previous, int type, double... coords) {
     37            this.previous = previous;
     38            this.type = type;
     39            this.coords = coords;
     40        }
     41       
     42        public SimplePathIterator(int type, double... coords) {
     43            this(null, type, coords);
     44        }
     45       
     46        @Override
     47        public int getWindingRule() {
     48            return WIND_NON_ZERO;
     49        }
     50       
     51        @Override
     52        public boolean isDone() {
     53            return done;
     54        }
     55       
     56        @Override
     57        public void next() {
     58            if (previous != null && !previous.isDone()) {
     59                previous.next();
     60            } else {
     61                done = true;
     62            }
     63        }
     64       
     65        @Override
     66        public int currentSegment(float[] coords) {
     67            if (previous != null && !previous.isDone()) {
     68                return previous.currentSegment(coords);
     69            } else if (done) {
     70                throw new NoSuchElementException("Iterator is already done.");
     71            }
     72           
     73            for (int i = 0; i < 6; ++i) {
     74                coords[i] = (float) this.coords[i];
     75            }
     76           
     77            return type;
     78        }
     79       
     80        @Override
     81        public int currentSegment(double[] coords) {
     82            if (previous != null && !previous.isDone()) {
     83                return previous.currentSegment(coords);
     84            } else if (done) {
     85                throw new NoSuchElementException("Iterator is already done.");
     86            }
     87           
     88            for (int i = 0; i < 6; ++i) {
     89                coords[i] = this.coords[i];
     90            }
     91           
     92            return type;
     93        }
     94       
     95    }
     96   
     97    private static final class Line extends Path {
     98        private final Path previous;
     99       
     100        private final double endX;
     101        private final double endY;
     102       
     103        private final double angle;
     104       
     105        private final double length;
     106       
     107        public Line(Path previous, double x, double y, double length) {
     108            this.previous = previous;
     109           
     110            this.endX = x;
     111            this.endY = y;
     112           
     113            this.angle = angle(previous.getEnd(), getEnd());
     114           
     115            this.length = length;
     116        }
     117       
     118        @Override
     119        public Point2D getStart() {
     120            return previous.getStart();
     121        }
     122       
     123        @Override
     124        public Point2D getEnd() {
     125            return new Point2D.Double(endX, endY);
     126        }
     127       
     128        @Override
     129        public double getEndAngle() {
     130            return angle;
     131        }
     132       
     133        @Override
     134        public double getLength() {
     135            return previous.getLength() + length;
     136        }
     137       
     138        @Override
     139        public Path offset(double ws, double m1, double m2, double we) {
     140            return offsetInternal(ws, m1, m2, we, angle);
     141        }
     142       
     143        @Override
     144        Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
     145            final double PL = previous.getLength();
     146            final double ML = PL + length;
     147           
     148            final Path prev = previous.offsetInternal(ws, m1, m2, we, angle);
     149           
     150            final double wStart = PL <= m1 ? ws : m2 <= PL ? we : ws + (PL - m1) * (we - ws) / (m2 - m1);
     151            final Point2D from = prev.getEnd();
     152            final Point2D to = offsetEnd(wStart, endAngle);
     153           
     154            if (abs(minAngleDiff(angle, angle(from, to))) > PI / 100) {
     155                return previous.offsetInternal(ws, m1, m2, we, endAngle);
     156            }
     157           
     158            if (ML <= m1) {
     159                return simpleOffset(prev, ws, endAngle);
     160            } else if (m2 <= PL) {
     161                return simpleOffset(prev, we, endAngle);
     162            }
     163           
     164            final double LL = from.distance(to);
     165           
     166            final Point2D m1o = PL <= m1 ? relativePoint(prev.getEnd(), LL * (m1 - PL) / length, angle) : null;
     167            final Point2D m2t = m2 <= ML ? relativePoint(getEnd(), LL * (ML - m2) / length, angle + PI) : null;
     168            final Point2D m2o = m2t == null ? null : relativePoint(m2t, we, (angle + endAngle - PI) / 2);
     169           
     170            if (m1o != null && m2o != null) {
     171                final Line l1 = new Line(prev, m1o.getX(), m1o.getY(), m1 - PL);
     172                final Line l2 = new Line(l1, m2o.getX(), m2o.getY(), m2 - m1);
     173               
     174                final Point2D end = offsetEnd(we, endAngle);
     175               
     176                return new Line(l2, end.getX(), end.getY(), ML - m2);
     177            } else if (m1o != null) {
     178                final Line l1 = new Line(prev, m1o.getX(), m1o.getY(), m1 - PL);
     179               
     180                final double w = ws + (ML - m1) * (we - ws) / (m2 - m1);
     181                final Point2D end = offsetEnd(w, endAngle);
     182               
     183                return new Line(l1, end.getX(), end.getY(), ML - m1);
     184            } else if (m2o != null) {
     185                final Line l2 = new Line(prev, m2o.getX(), m2o.getY(), m2 - PL);
     186               
     187                final Point2D end = offsetEnd(we, endAngle);
     188               
     189                return new Line(l2, end.getX(), end.getY(), ML - m2);
     190            } else {
     191                final double w = ws + (PL - m1 + length) * (we - ws) / (m2 - m1);
     192                final Point2D end = offsetEnd(w, endAngle);
     193                return new Line(prev, end.getX(), end.getY(), length);
     194            }
     195        }
     196       
     197        private Path simpleOffset(Path prev, double w, double endAngle) {
     198            final Point2D offset = offsetEnd(w, endAngle);
     199            return new Line(prev, offset.getX(), offset.getY(), length);
     200        }
     201       
     202        private Point2D offsetEnd(double w, double endAngle) {
     203            final double da2 = minAngleDiff(angle, endAngle) / 2;
     204            final double hypotenuse = w / cos(da2);
     205           
     206            return relativePoint(getEnd(), hypotenuse, angle + PI / 2 + da2);
     207        }
     208       
     209        @Override
     210        public SimplePathIterator getIterator() {
     211            return new SimplePathIterator(previous.getIteratorInternal(angle), PathIterator.SEG_LINETO, endX, endY, 0, 0, 0,
     212                0);
     213        }
     214       
     215        @Override
     216        public Path subpath(double from, double to) {
     217            final double PL = previous.getLength();
     218            final double ML = PL + length;
     219           
     220            if (to < PL) {
     221                return previous.subpath(from, to);
     222            }
     223           
     224            final Point2D end = to < ML ? getPoint(to) : new Point2D.Double(endX, endY);
     225           
     226            final double EL = min(ML, to);
     227            if (PL <= from) {
     228                final Point2D start = getPoint(from);
     229                return new Line(new Start(start.getX(), start.getY(), angle), end.getX(), end.getY(), EL - from);
     230            } else {
     231                return new Line(previous.subpath(from, to), end.getX(), end.getY(), EL - PL);
     232            }
     233        }
     234       
     235        @Override
     236        public Point2D getPoint(double offset) {
     237            final double PL = previous.getLength();
     238            final double ML = PL + length;
     239           
     240            if (offset <= ML && offset >= PL) {
     241                final double LL = previous.getEnd().distance(getEnd());
     242                return relativePoint(getEnd(), LL * (ML - offset) / length, angle + PI);
     243            } else {
     244                return previous.getPoint(offset);
     245            }
     246        }
     247       
     248        @Override
     249        SimplePathIterator getIteratorInternal(double endAngle) {
     250            return getIterator();
     251        }
     252    }
     253   
     254    // TODO curves are still somewhat broken
     255    private static class Curve extends Path {
     256        private final Path previous;
     257       
     258        private final double height;
     259       
     260        private final double centerX;
     261        private final double centerY;
     262        private final double centerToFromAngle;
     263       
     264        private final double endX;
     265        private final double endY;
     266       
     267        private final double fromAngle;
     268        private final double fromRadius;
     269        private final double toRadius;
     270        private final double angle;
     271       
     272        private final double length;
     273       
     274        private Curve(Path previous, double r1, double r2, double a, double length, double fromAngle) {
     275            this.previous = previous;
     276            this.fromAngle = fromAngle;
     277            this.fromRadius = r1;
     278            this.toRadius = r2;
     279            this.angle = a;
     280            this.length = length;
     281           
     282            final Point2D from = previous.getEnd();
     283            this.centerToFromAngle = fromAngle - signum(a) * PI / 2;
     284            final Point2D center = relativePoint(from, r1, centerToFromAngle + PI);
     285           
     286            final double toAngle = centerToFromAngle + a;
     287            this.endX = center.getX() + r2 * cos(toAngle);
     288            this.endY = center.getY() - r2 * sin(toAngle);
     289           
     290            this.centerX = center.getX();
     291            this.centerY = center.getY();
     292           
     293            final double y = new Line2D.Double(center, from).ptLineDist(endX, endY);
     294            this.height = y / sin(angle);
     295        }
     296       
     297        public Curve(Path previous, double r1, double r2, double a, double length) {
     298            this(previous, r1, r2, a, length, previous.getEndAngle());
     299        }
     300       
     301        public Point2D getStart() {
     302            return previous.getStart();
     303        }
     304       
     305        @Override
     306        public Point2D getEnd() {
     307            return new Point2D.Double(endX, endY);
     308        }
     309       
     310        @Override
     311        public double getEndAngle() {
     312            return fromAngle + angle;
     313        }
     314       
     315        @Override
     316        public double getLength() {
     317            return previous.getLength() + length;
     318        }
     319       
     320        @Override
     321        public Path offset(double ws, double m1, double m2, double we) {
     322            return offsetInternal(ws, m1, m2, we, previous.getEndAngle() + angle);
     323        }
     324       
     325        @Override
     326        Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
     327            final double PL = previous.getLength();
     328            final double ML = PL + length;
     329           
     330            final Path prev = previous.offsetInternal(ws, m1, m2, we, fromAngle);
     331           
     332            if (ML <= m1) {
     333                return simpleOffset(prev, ws);
     334            } else if (m2 <= PL) {
     335                return simpleOffset(prev, we);
     336            }
     337           
     338            final double s = signum(angle);
     339           
     340            if (PL < m1 && m2 < ML) {
     341                final double l1 = m1 - PL;
     342                final double a1 = angle(l1);
     343                final double r1 = radius(a1) - s * ws;
     344               
     345                final Curve c1 = new Curve(prev, fromRadius - ws, r1, offsetAngle(prev, a1), l1, fromAngle);
     346               
     347                final double l2 = m2 - m1;
     348                final double a2 = angle(l2);
     349                final double r2 = radius(a2) - s * we;
     350               
     351                final Curve c2 = new Curve(c1, r1, r2, a2 - a1, l2);
     352               
     353                return new Curve(c2, r2, toRadius - s * we, angle - a2, ML - m2);
     354            } else if (PL < m1) {
     355                final double l1 = m1 - PL;
     356                final double a1 = angle(l1);
     357                final double r1 = radius(a1) - s * ws;
     358               
     359                final Curve c1 = new Curve(prev, fromRadius - s * ws, r1, offsetAngle(prev, a1), l1, fromAngle);
     360               
     361                final double w = ws + (ML - m1) * (we - ws) / (m2 - m1);
     362               
     363                return new Curve(c1, r1, toRadius - s * w, angle - a1, ML - m1);
     364            } else if (m2 < ML) {
     365                final double w = ws + (PL - m1) * (we - ws) / (m2 - m1);
     366               
     367                final double l2 = m2 - PL;
     368                final double a2 = angle(l2);
     369                final double r2 = radius(a2) - s * we;
     370               
     371                final Curve c2 = new Curve(prev, fromRadius - s * w, r2, offsetAngle(prev, a2), l2, fromAngle);
     372               
     373                return new Curve(c2, r2, toRadius - s * we, angle - a2, ML - m2);
     374            } else {
     375                final double w1 = ws + (PL - m1) * (we - ws) / (m2 - m1);
     376                final double w2 = we - (m2 - ML) * (we - ws) / (m2 - m1);
     377               
     378                return new Curve(prev, fromRadius - s * w1, toRadius - s * w2, offsetAngle(prev, angle), length, fromAngle);
     379            }
     380        }
     381       
     382        private double angle(double l) {
     383            return l * angle / length;
     384        }
     385       
     386        private double radius(double a) {
     387            return hypot(fromRadius * cos(a), height * sin(a));
     388        }
     389       
     390        private double offsetAngle(Path prev, double a) {
     391            return a;// + GuiUtil.normalize(previous.getEndAngle()
     392            // - prev.getEndAngle());
     393        }
     394       
     395        private Path simpleOffset(Path prev, double w) {
     396            final double s = signum(angle);
     397            return new Curve(prev, fromRadius - s * w, toRadius - s * w, offsetAngle(prev, angle), length, fromAngle);
     398        }
     399       
     400        @Override
     401        public SimplePathIterator getIterator() {
     402            return getIteratorInternal(previous.getEndAngle() + angle);
     403        }
     404       
     405        @Override
     406        public Path subpath(double from, double to) {
     407            final double PL = previous.getLength();
     408            final double ML = PL + length;
     409           
     410            if (to < PL) {
     411                return previous.subpath(from, to);
     412            }
     413           
     414            final double toA = to < ML ? angle(to - PL) : angle;
     415            final double toR = to < ML ? radius(toA) : toRadius;
     416           
     417            final double fromA = from > PL ? angle(from - PL) : 0;
     418            final double fromR = from > PL ? radius(fromA) : fromRadius;
     419           
     420            final double a = toA - fromA;
     421            final double l = min(ML, to) - max(PL, from);
     422           
     423            if (from >= PL) {
     424                final Point2D start = getPoint(from);
     425                final double fa = fromAngle + fromA;
     426               
     427                return new Curve(new Start(start.getX(), start.getY(), fa), fromR, toR, a, l, fa);
     428            } else {
     429                return new Curve(previous.subpath(from, to), fromR, toR, a, l, fromAngle);
     430            }
     431        }
     432       
     433        @Override
     434        public Point2D getPoint(double offset) {
     435            final double PL = previous.getLength();
     436            final double ML = PL + length;
     437           
     438            if (offset <= ML && offset >= PL) {
     439                final double a = abs(angle(offset - PL));
     440                final double w = fromRadius * cos(a);
     441                final double h = -height * sin(a);
     442               
     443                final double r = centerToFromAngle; // rotation angle
     444                final double x = w * cos(r) + h * sin(r);
     445                final double y = -w * sin(r) + h * cos(r);
     446               
     447                return new Point2D.Double(centerX + x, centerY + y);
     448            } else {
     449                return previous.getPoint(offset);
     450            }
     451        }
     452       
     453        @Override
     454        SimplePathIterator getIteratorInternal(double endAngle) {
     455            final Point2D cp1 = relativePoint(previous.getEnd(), cpf(angle, fromRadius), previous.getEndAngle());
     456            final Point2D cp2 = relativePoint(getEnd(), cpf(angle, toRadius), endAngle + PI);
     457           
     458            return new SimplePathIterator(previous.getIteratorInternal(getEndAngle()), PathIterator.SEG_CUBICTO, //
     459                cp1.getX(), cp1.getY(), cp2.getX(), cp2.getY(), endX, endY //
     460            );
     461           
     462        }
     463    }
     464   
     465    private static class Start extends Path {
     466        private final double x;
     467        private final double y;
     468       
     469        private final double endAngle;
     470       
     471        public Start(double x, double y, double endAngle) {
     472            this.x = x;
     473            this.y = y;
     474            this.endAngle = endAngle;
     475        }
     476       
     477        public Start(double x, double y) {
     478            this(x, y, Double.NaN);
     479        }
     480       
     481        public Point2D getStart() {
     482            return new Point2D.Double(x, y);
     483        }
     484       
     485        public Point2D getEnd() {
     486            return new Point2D.Double(x, y);
     487        }
     488       
     489        @Override
     490        public double getEndAngle() {
     491            if (Double.isNaN(endAngle)) {
     492                throw new UnsupportedOperationException();
     493            }
     494           
     495            return endAngle;
     496        }
     497       
     498        @Override
     499        public double getLength() {
     500            return 0;
     501        }
     502       
     503        @Override
     504        public Path offset(double ws, double m1, double m2, double we) {
     505            throw new UnsupportedOperationException();
     506        }
     507       
     508        @Override
     509        Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
     510            final Point2D offset = relativePoint(getStart(), ws, endAngle + PI / 2);
     511            return new Start(offset.getX(), offset.getY(), endAngle);
     512        }
     513       
     514        @Override
     515        public SimplePathIterator getIterator() {
     516            return new SimplePathIterator(PathIterator.SEG_MOVETO, x, y, 0, 0, 0, 0);
     517        }
     518       
     519        @Override
     520        public Path subpath(double from, double to) {
     521            if (from > to) {
     522                throw new IllegalArgumentException("from > to");
     523            }
     524            if (from < 0) {
     525                throw new IllegalArgumentException("from < 0");
     526            }
     527           
     528            return this;
     529        }
     530       
     531        @Override
     532        public Point2D getPoint(double offset) {
     533            if (offset == 0) {
     534                return getEnd();
     535            } else {
     536                throw new IllegalArgumentException(Double.toString(offset));
     537            }
     538        }
     539       
     540        @Override
     541        SimplePathIterator getIteratorInternal(double endAngle) {
     542            return new SimplePathIterator(PathIterator.SEG_MOVETO, x, y, 0, 0, 0, 0);
     543        }
     544    }
     545   
     546    public static Path create(double x, double y) {
     547        return new Start(x, y);
     548    }
     549   
     550    public Path lineTo(double x, double y, double length) {
     551        return new Line(this, x, y, length);
     552    }
     553   
     554    public Path curveTo(double r1, double r2, double a, double length) {
     555        return new Curve(this, r1, r2, a, length);
     556    }
     557   
     558    public abstract Path offset(double ws, double m1, double m2, double we);
     559   
     560    abstract Path offsetInternal(double ws, double m1, double m2, double we, double endAngle);
     561   
     562    public abstract double getLength();
     563   
     564    public abstract double getEndAngle();
     565   
     566    public abstract Point2D getStart();
     567   
     568    public abstract Point2D getEnd();
     569   
     570    public abstract SimplePathIterator getIterator();
     571   
     572    abstract SimplePathIterator getIteratorInternal(double endAngle);
     573   
     574    public abstract Path subpath(double from, double to);
     575   
     576    public abstract Point2D getPoint(double offset);
    577577}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ReversePathIterator.java

    r25606 r26154  
    1616 */
    1717class ReversePathIterator implements PathIterator {
    18         private static final int[] COUNTS = {
    19             2, // SEG_MOVETO = 0
    20             2, // SEG_LINETO = 1
    21             4, // SEG_QUADTO = 2
    22             6, // SEG_CUBICTO = 3
    23             0, // SEG_CLOSE = 4
    24         };
    25        
    26         public static ReversePathIterator reverse(PathIterator it) {
    27                 return new ReversePathIterator(it);
    28         }
    29        
    30         private static int[] reverseTypes(int[] types, int length) {
    31                 if (length > 0 && types[0] != SEG_MOVETO) {
    32                         // the last segment of the reversed path is not defined
    33                         throw new IllegalArgumentException("Can not reverse path without initial SEG_MOVETO.");
    34                 }
    35                
    36                 final int[] result = new int[length];
    37                
    38                 result[0] = SEG_MOVETO;
    39                
    40                 int lower = 1;
    41                 int upper = length - 1;
    42                
    43                 while (lower <= upper) {
    44                         result[lower] = types[upper];
    45                         result[upper] = types[lower];
    46                        
    47                         ++lower;
    48                         --upper;
    49                 }
    50                
    51                 return result;
    52         }
    53        
    54         private static double[] reverseCoords(double[] coords, int length) {
    55                 final double[] result = new double[length];
    56                
    57                 int lower = 0;
    58                 int upper = length - 2;
    59                
    60                 while (lower <= upper) {
    61                         result[lower] = coords[upper];
    62                         result[lower + 1] = coords[upper + 1];
    63                         result[upper] = coords[lower];
    64                         result[upper + 1] = coords[lower + 1];
    65                        
    66                         lower += 2;
    67                         upper -= 2;
    68                 }
    69                
    70                 return result;
    71         }
    72        
    73         private final int winding;
    74        
    75         private final int[] types;
    76         private int typesIndex = 0;
    77        
    78         private final double[] coords;
    79         private int coordsIndex = 0;
    80        
    81         private ReversePathIterator(PathIterator it) {
    82                 this.winding = it.getWindingRule();
    83                
    84                 double[] tmpCoords = new double[62];
    85                 int[] tmpTypes = new int[11];
    86                
    87                 int tmpCoordsI = 0;
    88                 int tmpTypesI = 0;
    89                
    90                 while (!it.isDone()) {
    91                         if (tmpTypesI >= tmpTypes.length) {
    92                                 tmpTypes = Arrays.copyOf(tmpTypes, 2 * tmpTypes.length);
    93                         }
    94                        
    95                         final double[] cs = new double[6];
    96                         final int t = it.currentSegment(cs);
    97                         tmpTypes[tmpTypesI++] = t;
    98                         final int count = COUNTS[t];
    99                        
    100                         if (tmpCoordsI + count > tmpCoords.length) {
    101                                 tmpCoords = Arrays.copyOf(tmpCoords, 2 * tmpCoords.length);
    102                         }
    103                         System.arraycopy(cs, 0, tmpCoords, tmpCoordsI, count);
    104                         tmpCoordsI += count;
    105                        
    106                         it.next();
    107                 }
    108                
    109                 this.types = reverseTypes(tmpTypes, tmpTypesI);
    110                 this.coords = reverseCoords(tmpCoords, tmpCoordsI);
    111         }
    112        
    113         @Override
    114         public int getWindingRule() {
    115                 return winding;
    116         }
    117        
    118         @Override
    119         public boolean isDone() {
    120                 return typesIndex >= types.length;
    121         }
    122        
    123         @Override
    124         public void next() {
    125                 coordsIndex += COUNTS[types[typesIndex]];
    126                 ++typesIndex;
    127         }
    128        
    129         @Override
    130         public int currentSegment(float[] coords) {
    131                 final double[] tmp = new double[6];
    132                 final int type = currentSegment(tmp);
    133                
    134                 coords[0] = (float) tmp[0];
    135                 coords[1] = (float) tmp[1];
    136                 coords[2] = (float) tmp[2];
    137                 coords[3] = (float) tmp[3];
    138                 coords[4] = (float) tmp[4];
    139                 coords[5] = (float) tmp[5];
    140                
    141                 return type;
    142         }
    143        
    144         @Override
    145         public int currentSegment(double[] coords) {
    146                 final int type = types[typesIndex];
    147                 System.arraycopy(this.coords, coordsIndex, coords, 0, COUNTS[type]);
    148                 return type;
    149         }
     18    private static final int[] COUNTS = {
     19        2, // SEG_MOVETO = 0
     20        2, // SEG_LINETO = 1
     21        4, // SEG_QUADTO = 2
     22        6, // SEG_CUBICTO = 3
     23        0, // SEG_CLOSE = 4
     24    };
     25   
     26    public static ReversePathIterator reverse(PathIterator it) {
     27        return new ReversePathIterator(it);
     28    }
     29   
     30    private static int[] reverseTypes(int[] types, int length) {
     31        if (length > 0 && types[0] != SEG_MOVETO) {
     32            // the last segment of the reversed path is not defined
     33            throw new IllegalArgumentException("Can not reverse path without initial SEG_MOVETO.");
     34        }
     35       
     36        final int[] result = new int[length];
     37       
     38        result[0] = SEG_MOVETO;
     39       
     40        int lower = 1;
     41        int upper = length - 1;
     42       
     43        while (lower <= upper) {
     44            result[lower] = types[upper];
     45            result[upper] = types[lower];
     46           
     47            ++lower;
     48            --upper;
     49        }
     50       
     51        return result;
     52    }
     53   
     54    private static double[] reverseCoords(double[] coords, int length) {
     55        final double[] result = new double[length];
     56       
     57        int lower = 0;
     58        int upper = length - 2;
     59       
     60        while (lower <= upper) {
     61            result[lower] = coords[upper];
     62            result[lower + 1] = coords[upper + 1];
     63            result[upper] = coords[lower];
     64            result[upper + 1] = coords[lower + 1];
     65           
     66            lower += 2;
     67            upper -= 2;
     68        }
     69       
     70        return result;
     71    }
     72   
     73    private final int winding;
     74   
     75    private final int[] types;
     76    private int typesIndex = 0;
     77   
     78    private final double[] coords;
     79    private int coordsIndex = 0;
     80   
     81    private ReversePathIterator(PathIterator it) {
     82        this.winding = it.getWindingRule();
     83       
     84        double[] tmpCoords = new double[62];
     85        int[] tmpTypes = new int[11];
     86       
     87        int tmpCoordsI = 0;
     88        int tmpTypesI = 0;
     89       
     90        while (!it.isDone()) {
     91            if (tmpTypesI >= tmpTypes.length) {
     92                tmpTypes = Arrays.copyOf(tmpTypes, 2 * tmpTypes.length);
     93            }
     94           
     95            final double[] cs = new double[6];
     96            final int t = it.currentSegment(cs);
     97            tmpTypes[tmpTypesI++] = t;
     98            final int count = COUNTS[t];
     99           
     100            if (tmpCoordsI + count > tmpCoords.length) {
     101                tmpCoords = Arrays.copyOf(tmpCoords, 2 * tmpCoords.length);
     102            }
     103            System.arraycopy(cs, 0, tmpCoords, tmpCoordsI, count);
     104            tmpCoordsI += count;
     105           
     106            it.next();
     107        }
     108       
     109        this.types = reverseTypes(tmpTypes, tmpTypesI);
     110        this.coords = reverseCoords(tmpCoords, tmpCoordsI);
     111    }
     112   
     113    @Override
     114    public int getWindingRule() {
     115        return winding;
     116    }
     117   
     118    @Override
     119    public boolean isDone() {
     120        return typesIndex >= types.length;
     121    }
     122   
     123    @Override
     124    public void next() {
     125        coordsIndex += COUNTS[types[typesIndex]];
     126        ++typesIndex;
     127    }
     128   
     129    @Override
     130    public int currentSegment(float[] coords) {
     131        final double[] tmp = new double[6];
     132        final int type = currentSegment(tmp);
     133       
     134        coords[0] = (float) tmp[0];
     135        coords[1] = (float) tmp[1];
     136        coords[2] = (float) tmp[2];
     137        coords[3] = (float) tmp[3];
     138        coords[4] = (float) tmp[4];
     139        coords[5] = (float) tmp[5];
     140       
     141        return type;
     142    }
     143   
     144    @Override
     145    public int currentSegment(double[] coords) {
     146        final int type = types[typesIndex];
     147        System.arraycopy(this.coords, coordsIndex, coords, 0, COUNTS[type]);
     148        return type;
     149    }
    150150}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/RoadGui.java

    r25783 r26154  
    4141
    4242class 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        
    118         private final class Extender extends InteractiveElement {
    119                 private final Road.End end;
    120                 private final Way way;
    121                
    122                 private final Line2D line;
    123                
    124                 public Extender(Road.End end, Way way, double angle) {
    125                         this.end = end;
    126                         this.way = way;
    127                         this.line = new Line2D.Double(a.getPoint(), relativePoint(a.getPoint(), getContainer().getLaneWidth() * 4, angle));
    128                 }
    129                
    130                 @Override
    131                 void paint(Graphics2D g2d, State state) {
    132                         g2d.setStroke(getContainer().getConnectionStroke());
    133                         g2d.setColor(Color.CYAN);
    134                         g2d.draw(line);
    135                 }
    136                
    137                 @Override
    138                 boolean contains(Point2D p, State state) {
    139                         final BasicStroke stroke = (BasicStroke) getContainer().getConnectionStroke();
    140                         final double strokeWidth = stroke.getLineWidth();
    141                        
    142                         final Point2D closest = closest(line, p);
    143                         return p.distance(closest) <= strokeWidth / 2;
    144                 }
    145                
    146                 @Override
    147                 State click(State old) {
    148                         end.extend(way);
    149                         return new State.Invalid(old);
    150                 }
    151                
    152                 @Override
    153                 Type getType() {
    154                         return Type.EXTENDER;
    155                 }
    156                
    157                 @Override
    158                 int getZIndex() {
    159                         return 0;
    160                 }
    161         }
    162        
    163         private final class LaneAdder extends InteractiveElement {
    164                 private final Road.End end;
    165                 private final Lane.Kind kind;
    166                
    167                 private final Point2D center;
    168                 private final Ellipse2D background;
    169                
    170                 public LaneAdder(Road.End end, Lane.Kind kind) {
    171                         this.end = end;
    172                         this.kind = kind;
    173                        
    174                         final double a = getAngle(end) + PI;
    175                         final Point2D lc = getLeftCorner(end);
    176                         final Point2D rc = getRightCorner(end);
    177                        
    178                         final double r = connectorRadius;
    179                         final double cx;
    180                         final double cy;
    181                         if (kind == Lane.Kind.EXTRA_LEFT) {
    182                                 final JunctionGui j = getContainer().getGui(end.getJunction());
    183                                 final Point2D i = intersection(line(j.getPoint(), a), new Line2D.Double(lc, rc));
    184                                
    185                                 cx = i.getX() + 21d / 16 * r * (2 * cos(a) + cos(a - PI / 2));
    186                                 cy = i.getY() - 21d / 16 * r * (2 * sin(a) + sin(a - PI / 2));
    187                         } else {
    188                                 cx = rc.getX() + 21d / 16 * r * (2 * cos(a) + cos(a + PI / 2));
    189                                 cy = rc.getY() - 21d / 16 * r * (2 * sin(a) + sin(a + PI / 2));
    190                         }
    191                        
    192                         center = new Point2D.Double(cx, cy);
    193                         background = new Ellipse2D.Double(cx - r, cy - r, 2 * r, 2 * r);
    194                 }
    195                
    196                 @Override
    197                 void paint(Graphics2D g2d, State state) {
    198                         if (!isVisible(state)) {
    199                                 return;
    200                         }
    201                        
    202                         g2d.setColor(Color.DARK_GRAY);
    203                         g2d.fill(background);
    204                        
    205                         final double l = 2 * connectorRadius / 3;
    206                         final Line2D v = new Line2D.Double(center.getX(), center.getY() - l, center.getX(), center.getY() + l);
    207                         final Line2D h = new Line2D.Double(center.getX() - l, center.getY(), center.getX() + l, center.getY());
    208                        
    209                         g2d.setStroke(new BasicStroke((float) (connectorRadius / 5)));
    210                         g2d.setColor(Color.WHITE);
    211                         g2d.draw(v);
    212                         g2d.draw(h);
    213                 }
    214                
    215                 private boolean isVisible(State state) {
    216                         return end.getJunction().isPrimary();
    217                 }
    218                
    219                 @Override
    220                 boolean contains(Point2D p, State state) {
    221                         return isVisible(state) && background.contains(p);
    222                 }
    223                
    224                 @Override
    225                 Type getType() {
    226                         return Type.LANE_ADDER;
    227                 }
    228                
    229                 @Override
    230                 int getZIndex() {
    231                         return 2;
    232                 }
    233                
    234                 @Override
    235                 public State click(State old) {
    236                         end.addLane(kind);
    237                         return new State.Invalid(old);
    238                 }
    239         }
    240        
    241         final class IncomingConnector extends InteractiveElement {
    242                 private final Road.End end;
    243                 private final List<LaneGui> lanes;
    244                
    245                 private final Point2D center = new Point2D.Double();
    246                 private final Ellipse2D circle = new Ellipse2D.Double();
    247                
    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);
    256                 }
    257                
    258                 @Override
    259                 public void paintBackground(Graphics2D g2d, State state) {
    260                         if (isActive(state)) {
    261                                 final Composite old = g2d.getComposite();
    262                                 g2d.setComposite(((AlphaComposite) old).derive(0.2f));
    263                                
    264                                 g2d.setColor(new Color(255, 127, 31));
    265                                
    266                                 for (LaneGui l : lanes) {
    267                                         l.fill(g2d);
    268                                 }
    269                                
    270                                 g2d.setComposite(old);
    271                         }
    272                 }
    273                
    274                 @Override
    275                 public void paint(Graphics2D g2d, State state) {
    276                         if (isVisible(state)) {
    277                                 final Composite old = g2d.getComposite();
    278                                 if (isActive(state)) {
    279                                         g2d.setComposite(((AlphaComposite) old).derive(1f));
    280                                 }
    281                                
    282                                 g2d.setColor(Color.LIGHT_GRAY);
    283                                 g2d.fill(circle);
    284                                
    285                                 g2d.setComposite(old);
    286                         }
    287                 }
    288                
    289                 private boolean isActive(State state) {
    290                         if (!(state instanceof State.IncomingActive)) {
    291                                 return false;
    292                         }
    293                        
    294                         final Road.End roadEnd = ((State.IncomingActive) state).getRoadEnd();
    295                        
    296                         return roadEnd.equals(getRoadEnd());
    297                 }
    298                
    299                 private boolean isVisible(State state) {
    300                         if (getModel().isPrimary() || !getRoadEnd().getJunction().isPrimary()
    301                             || getRoadEnd().getOppositeEnd().getLanes().isEmpty()) {
    302                                 return false;
    303                         }
    304                        
    305                         if (state instanceof State.Connecting) {
    306                                 return ((State.Connecting) state).getJunction().equals(getRoadEnd().getJunction());
    307                         }
    308                        
    309                         return true;
    310                 }
    311                
    312                 @Override
    313                 public boolean contains(Point2D p, State state) {
    314                         if (!isVisible(state)) {
    315                                 return false;
    316                         } else if (circle.contains(p)) {
    317                                 return true;
    318                         }
    319                        
    320                         for (LaneGui l : lanes) {
    321                                 if (l.contains(p)) {
    322                                         return true;
    323                                 }
    324                         }
    325                        
    326                         return false;
    327                 }
    328                
    329                 @Override
    330                 public Type getType() {
    331                         return Type.INCOMING_CONNECTOR;
    332                 }
    333                
    334                 @Override
    335                 public State activate(State old) {
    336                         return new State.IncomingActive(getRoadEnd());
    337                 }
    338                
    339                 public Point2D getCenter() {
    340                         return (Point2D) center.clone();
    341                 }
    342                
    343                 void move(double x, double y) {
    344                         final double r = connectorRadius;
    345                        
    346                         center.setLocation(x, y);
    347                         circle.setFrame(x - r, y - r, 2 * r, 2 * r);
    348                 }
    349                
    350                 public Road.End getRoadEnd() {
    351                         return end;
    352                 }
    353                
    354                 public List<LaneGui> getLanes() {
    355                         return lanes;
    356                 }
    357                
    358                 @Override
    359                 int getZIndex() {
    360                         return 1;
    361                 }
    362                
    363                 public void add(LaneGui lane) {
    364                         lanes.add(lane);
    365                 }
    366         }
    367        
    368         // TODO rework to be a SegmentGui (with getModel())
    369         private final class Segment {
    370                 final Point2D to;
    371                 final Point2D from;
    372                
    373                 final Segment prev;
    374                 final Segment next;
    375                
    376                 final double length;
    377                 final double angle;
    378                
    379                 public Segment(Segment next, List<Point2D> bends, JunctionGui a) {
    380                         final Point2D head = (Point2D) bends.get(0).clone();
    381                         final List<Point2D> tail = bends.subList(1, bends.size());
    382                        
    383                         this.next = next;
    384                         this.to = head;
    385                         this.from = (Point2D) (tail.isEmpty() ? a.getPoint() : tail.get(0)).clone();
    386                         this.prev = tail.isEmpty() ? null : new Segment(this, tail, a);
    387                         this.length = from.distance(to);
    388                         this.angle = angle(from, to);
    389                        
    390                         // TODO create a factory method for the segments list and pass it to
    391                         // the constructor(s)
    392                         segments.add(this);
    393                 }
    394                
    395                 public Segment(JunctionGui b, List<Point2D> bends, JunctionGui a) {
    396                         this((Segment) null, prepended(bends, (Point2D) b.getPoint().clone()), a);
    397                 }
    398                
    399                 private double getFromOffset() {
    400                         return prev == null ? 0 : prev.getFromOffset() + prev.length;
    401                 }
    402                
    403                 public double getOffset(double x, double y) {
    404                         return getOffsetInternal(new Point2D.Double(x, y), -1, Double.POSITIVE_INFINITY);
    405                 }
    406                
    407                 private double getOffsetInternal(Point2D p, double offset, double quality) {
    408                         final Point2D closest = closest(new Line2D.Double(from, to), p);
    409                         final double myQuality = closest.distance(p);
    410                        
    411                         if (myQuality < quality) {
    412                                 quality = myQuality;
    413                                
    414                                 final Line2D normal = line(p, angle + PI / 2);
    415                                 final Point2D isect = intersection(normal, new Line2D.Double(from, to));
    416                                 final double d = from.distance(isect);
    417                                 final boolean negative = Math.abs(angle(from, isect) - angle) > 1;
    418                                
    419                                 offset = getFromOffset() + (negative ? -1 : 1) * d;
    420                         }
    421                        
    422                         return next == null ? offset : next.getOffsetInternal(p, offset, quality);
    423                 }
    424                
    425                 public Path append(Path path, boolean forward, double offset) {
    426                         if (ROUND_CORNERS) {
    427                                 final Segment n = forward ? prev : next;
    428                                 final Point2D s = forward ? to : from;
    429                                 final Point2D e = forward ? from : to;
    430                                
    431                                 if (n == null) {
    432                                         return path.lineTo(e.getX(), e.getY(), length - offset);
    433                                 }
    434                                
    435                                 final double a = minAngleDiff(angle, n.angle);
    436                                 final double d = 3 * outerMargin + getWidth(getModel().getToEnd(), (forward && a < 0) || (!forward && a > 0));
    437                                 final double l = d * tan(abs(a));
    438                                
    439                                 if (length - offset < l / 2 || n.length < l / 2) {
    440                                         return n.append(path.lineTo(e.getX(), e.getY(), length - offset), forward, 0);
    441                                 } else {
    442                                         final Point2D p = relativePoint(e, l / 2, angle(e, s));
    443                                        
    444                                         final Path line = path.lineTo(p.getX(), p.getY(), length - l / 2 - offset);
    445                                         final Path curve = line.curveTo(d, d, a, l);
    446                                        
    447                                         return n.append(curve, forward, l / 2);
    448                                 }
    449                         } else if (forward) {
    450                                 final Path tmp = path.lineTo(from.getX(), from.getY(), length);
    451                                 return prev == null ? tmp : prev.append(tmp, forward, 0);
    452                         } else {
    453                                 final Path tmp = path.lineTo(to.getX(), to.getY(), length);
    454                                 return next == null ? tmp : next.append(tmp, forward, 0);
    455                         }
    456                 }
    457         }
    458        
    459         /**
    460         * This should become a setting, but rounding is (as of yet) still slightly buggy and a low
    461         * priority.
    462         */
    463         private static final boolean ROUND_CORNERS = false;
    464        
    465         private static final List<Point2D> prepended(List<Point2D> bends, Point2D point) {
    466                 final List<Point2D> result = new ArrayList<Point2D>(bends.size() + 1);
    467                 result.add(point);
    468                 result.addAll(bends);
    469                 return result;
    470         }
    471        
    472         private final GuiContainer container;
    473         private final double innerMargin;
    474         private final double outerMargin;
    475        
    476         private final float lineWidth;
    477         private final Stroke regularStroke;
    478         private final Stroke dashedStroke;
    479        
    480         private final JunctionGui a;
    481         private final JunctionGui b;
    482         private final double length;
    483        
    484         private final IncomingConnector incomingA;
    485         private final IncomingConnector incomingB;
    486        
    487         private final Road road;
    488         private final List<Segment> segments = new ArrayList<Segment>();
    489        
    490         final double connectorRadius;
    491        
    492         public RoadGui(GuiContainer container, Road road) {
    493                 this.container = container;
    494                
    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());
    502                
    503                 final List<Point2D> bends = new ArrayList<Point2D>();
    504                 final List<Node> nodes = road.getRoute().getNodes();
    505                 for (int i = nodes.size() - 2; i > 0; --i) {
    506                         bends.add(container.translateAndScale(loc(nodes.get(i))));
    507                 }
    508                
    509                 // they add themselves to this.segments
    510                 new Segment(b, bends, a);
    511                 double l = 0;
    512                 for (Segment s : segments) {
    513                         l += s.length;
    514                 }
    515                 this.length = l;
    516                
    517                 this.innerMargin = !incomingA.getLanes().isEmpty() && !incomingB.getLanes().isEmpty() ? 1 * container
    518                     .getLaneWidth() / 15 : 0;
    519                 this.outerMargin = container.getLaneWidth() / 6;
    520                 this.connectorRadius = 3 * container.getLaneWidth() / 8;
    521                 this.lineWidth = (float) (container.getLaneWidth() / 30);
    522                 this.regularStroke = new BasicStroke(2 * lineWidth);
    523                 this.dashedStroke = new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10f, new float[] {
    524                     (float) (container.getLaneWidth() / 2), (float) (container.getLaneWidth() / 3)
    525                 }, 0);
    526         }
    527        
    528         public JunctionGui getA() {
    529                 return a;
    530         }
    531        
    532         public JunctionGui getB() {
    533                 return b;
    534         }
    535        
    536         public Line2D getLeftCurb(Road.End end) {
    537                 return GuiUtil.line(getCorner(end, true), getAngle(end) + PI);
    538         }
    539        
    540         public Line2D getRightCurb(Road.End end) {
    541                 return GuiUtil.line(getCorner(end, false), getAngle(end) + PI);
    542         }
    543        
    544         private Point2D getLeftCorner(Road.End end) {
    545                 return getCorner(end, true);
    546         }
    547        
    548         private Point2D getRightCorner(Road.End end) {
    549                 return getCorner(end, false);
    550         }
    551        
    552         private Point2D getCorner(Road.End end, boolean left) {
    553                 final JunctionGui j = end.isFromEnd() ? a : b;
    554                 final double w = left ? getWidth(end, true) : getWidth(end, false);
    555                 final double s = (left ? 1 : -1);
    556                 final double a = getAngle(end) + PI;
    557                 final double t = left ? j.getLeftTrim(end) : j.getRightTrim(end);
    558                
    559                 final double dx = s * cos(PI / 2 - a) * w + cos(a) * t;
    560                 final double dy = s * sin(PI / 2 - a) * w - sin(a) * t;
    561                
    562                 return new Point2D.Double(j.x + dx, j.y + dy);
    563         }
    564        
    565         private double getWidth(Road.End end, boolean left) {
    566                 if (!end.getRoad().equals(road)) {
    567                         throw new IllegalArgumentException();
    568                 }
    569                
    570                 final int lcForward = incomingA.getLanes().size();
    571                 final int lcBackward = incomingB.getLanes().size();
    572                
    573                 final double LW = getContainer().getLaneWidth();
    574                 final double M = innerMargin + outerMargin;
    575                
    576                 if (end.isToEnd()) {
    577                         return (left ? lcBackward : lcForward) * LW + M;
    578                 } else {
    579                         return (left ? lcForward : lcBackward) * LW + M;
    580                 }
    581         }
    582        
    583         List<InteractiveElement> paint(Graphics2D g2d) {
    584                 final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
    585                
    586                 result.addAll(paintLanes(g2d));
    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                 }
    596                
    597                 g2d.setColor(Color.RED);
    598                 for (Segment s : segments) {
    599                         g2d.fill(new Ellipse2D.Double(s.from.getX() - 1, s.from.getY() - 1, 2, 2));
    600                 }
    601                
    602                 return result;
    603         }
    604        
    605         private List<LaneAdder> laneAdders() {
    606                 final List<LaneAdder> result = new ArrayList<LaneAdder>(4);
    607                
    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));
    616                 }
    617                
    618                 return result;
    619         }
    620        
    621         private List<Extender> extenders(Road.End end) {
    622                 if (!end.isExtendable()) {
    623                         return Collections.emptyList();
    624                 }
    625                
    626                 final List<Extender> result = new ArrayList<Extender>();
    627                
    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)) {
    631                                 final Node nextNode = w.firstNode().equals(n) ? w.getNode(1) : w.getNode(w.getNodesCount() - 2);
    632                                 final Point2D nextNodeLoc = getContainer().translateAndScale(loc(nextNode));
    633                                 result.add(new Extender(end, w, angle(a.getPoint(), nextNodeLoc)));
    634                         }
    635                 }
    636                
    637                 return result;
    638         }
    639        
    640         public Road getModel() {
    641                 return road;
    642         }
    643        
    644         public IncomingConnector getConnector(Road.End end) {
    645                 return end.isFromEnd() ? incomingA : incomingB;
    646         }
    647        
    648         private List<InteractiveElement> paintLanes(Graphics2D g2d) {
    649                 final Path2D middleLines = new Path2D.Double();
    650                
    651                 g2d.setStroke(regularStroke);
    652                
    653                 final boolean forward = !incomingA.getLanes().isEmpty();
    654                 final boolean backward = !incomingB.getLanes().isEmpty();
    655                
    656                 final Path2D middleArea;
    657                 if (forward && backward) {
    658                         paintLanes(g2d, middleLines, true);
    659                         paintLanes(g2d, middleLines, false);
    660                        
    661                         middleLines.closePath();
    662                         middleArea = middleLines;
    663                         g2d.setColor(new Color(160, 160, 160));
    664                 } else if (forward || backward) {
    665                         paintLanes(g2d, middleLines, forward);
    666                        
    667                         middleArea = new Path2D.Double();
    668                         middleArea.append(middleLines.getPathIterator(null), false);
    669                         middleArea.append(middlePath(backward).offset(outerMargin, -1, -1, outerMargin).getIterator(), true);
    670                         middleArea.closePath();
    671                         g2d.setColor(Color.GRAY);
    672                 } else {
    673                         throw new AssertionError();
    674                 }
    675                
    676                 g2d.fill(middleArea);
    677                 g2d.setColor(Color.WHITE);
    678                 g2d.draw(middleLines);
    679                
    680                 final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
    681                
    682                 moveIncoming(getModel().getFromEnd());
    683                 moveIncoming(getModel().getToEnd());
    684                 result.add(incomingA);
    685                 result.add(incomingB);
    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                                 }
    696                         }
    697                 }
    698                
    699                 return result;
    700         }
    701        
    702         private void paintLanes(Graphics2D g2d, Path2D middleLines, boolean forward) {
    703                 final Shape clip = clip();
    704                 g2d.clip(clip);
    705                
    706                 final Path middle = middlePath(forward);
    707                
    708                 Path innerPath = middle.offset(innerMargin, -1, -1, innerMargin);
    709                 final List<Path> linePaths = new ArrayList<Path>();
    710                 linePaths.add(innerPath);
    711                
    712                 for (LaneGui l : forward ? incomingA.getLanes() : incomingB.getLanes()) {
    713                         l.setClip(clip);
    714                         innerPath = l.recalculate(innerPath, middleLines);
    715                         linePaths.add(innerPath);
    716                 }
    717                
    718                 final Path2D area = new Path2D.Double();
    719                 area(area, middle, innerPath.offset(outerMargin, -1, -1, outerMargin));
    720                 g2d.setColor(Color.GRAY);
    721                 g2d.fill(area);
    722                
    723                 g2d.setColor(Color.WHITE);
    724                 final Path2D lines = new Path2D.Double();
    725                 lines.append(innerPath.getIterator(), false);
    726                 g2d.draw(lines);
    727                
    728                 // g2d.setColor(new Color(32, 128, 192));
    729                 g2d.setColor(Color.WHITE);
    730                 g2d.setStroke(dashedStroke);
    731                 for (Path p : linePaths) {
    732                         lines.reset();
    733                         lines.append(p.getIterator(), false);
    734                         g2d.draw(lines);
    735                 }
    736                 g2d.setStroke(regularStroke);
    737                
    738                 // g2d.setColor(new Color(32, 128, 192));
    739                 // lines.reset();
    740                 // lines.append(middle.getIterator(), false);
    741                 // g2d.draw(lines);
    742                
    743                 g2d.setClip(null);
    744         }
    745        
    746         private Shape clip() {
    747                 final Area clip = new Area(new Rectangle2D.Double(-100000, -100000, 200000, 200000));
    748                 clip.subtract(new Area(negativeClip(true)));
    749                 clip.subtract(new Area(negativeClip(false)));
    750                
    751                 return clip;
    752         }
    753        
    754         private Shape negativeClip(boolean forward) {
    755                 final Road.End end = forward ? getModel().getToEnd() : getModel().getFromEnd();
    756                 final JunctionGui j = forward ? b : a;
    757                
    758                 final Line2D lc = getLeftCurb(end);
    759                 final Line2D rc = getRightCurb(end);
    760                
    761                 final Path2D negativeClip = new Path2D.Double();
    762                
    763                 final double d = rc.getP1().distance(j.getPoint()) + lc.getP1().distance(j.getPoint());
    764                
    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);
    772                
    773                 negativeClip.moveTo(r1.getX(), r1.getY());
    774                 negativeClip.lineTo(r2.getX(), r2.getY());
    775                 negativeClip.lineTo(l2.getX(), l2.getY());
    776                 negativeClip.lineTo(l1.getX(), l1.getY());
    777                 negativeClip.closePath();
    778                
    779                 return negativeClip;
    780         }
    781        
    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;
    788         }
    789        
    790         private Path middlePath(boolean forward) {
    791                 final Path path = forward ? Path.create(b.x, b.y) : Path.create(a.x, a.y);
    792                 final Segment first = forward ? segments.get(segments.size() - 1) : segments.get(0);
    793                
    794                 return first.append(path, forward, 0);
    795         }
    796        
    797         private void moveIncoming(Road.End end) {
    798                 final Point2D lc = getLeftCorner(end);
    799                 final Point2D rc = getRightCorner(end);
    800                 final Line2D cornerLine = new Line2D.Double(lc, rc);
    801                
    802                 final double a = getAngle(end);
    803                 final Line2D roadLine = line(getContainer().getGui(end.getJunction()).getPoint(), a);
    804                
    805                 final Point2D i = intersection(roadLine, cornerLine);
    806                 // TODO fix depending on angle(i, lc)
    807                 final double offset = innerMargin + (getWidth(end, true) - innerMargin - outerMargin) / 2;
    808                 final Point2D loc = relativePoint(i, offset, angle(i, lc));
    809                
    810                 getConnector(end).move(loc.getX(), loc.getY());
    811         }
    812        
    813         private void moveOutgoing(LaneGui lane, int offset) {
    814                 final Road.End end = lane.getModel().getOutgoingRoadEnd();
    815                
    816                 final Point2D lc = getLeftCorner(end);
    817                 final Point2D rc = getRightCorner(end);
    818                 final Line2D cornerLine = new Line2D.Double(lc, rc);
    819                
    820                 final double a = getAngle(end);
    821                 final Line2D roadLine = line(getContainer().getGui(end.getJunction()).getPoint(), a);
    822                
    823                 final Point2D i = intersection(roadLine, cornerLine);
    824                 // TODO fix depending on angle(i, rc)
    825                 final double d = innerMargin + (2 * offset + 1) * getContainer().getLaneWidth() / 2;
    826                 final Point2D loc = relativePoint(i, d, angle(i, rc));
    827                
    828                 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();
    837         }
    838        
    839         public double getAngle(Road.End end) {
    840                 if (!getModel().equals(end.getRoad())) {
    841                         throw new IllegalArgumentException();
    842                 }
    843                
    844                 if (end.isToEnd()) {
    845                         return segments.get(segments.size() - 1).angle;
    846                 } else {
    847                         final double angle = segments.get(0).angle;
    848                         return angle > PI ? angle - PI : angle + PI;
    849                 }
    850         }
    851        
    852         public double getWidth(Road.End end) {
    853                 return getWidth(end, true) + getWidth(end, false);
    854         }
    855        
    856         public double getLength() {
    857                 return length;
    858         }
    859        
    860         public double getOffset(double x, double y) {
    861                 return segments.get(0).getOffset(x, y);
    862         }
    863        
    864         public GuiContainer getContainer() {
    865                 return container;
    866         }
    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         }
     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   
     118    private final class Extender extends InteractiveElement {
     119        private final Road.End end;
     120        private final Way way;
     121       
     122        private final Line2D line;
     123       
     124        public Extender(Road.End end, Way way, double angle) {
     125            this.end = end;
     126            this.way = way;
     127            this.line = new Line2D.Double(a.getPoint(), relativePoint(a.getPoint(), getContainer().getLaneWidth() * 4, angle));
     128        }
     129       
     130        @Override
     131        void paint(Graphics2D g2d, State state) {
     132            g2d.setStroke(getContainer().getConnectionStroke());
     133            g2d.setColor(Color.CYAN);
     134            g2d.draw(line);
     135        }
     136       
     137        @Override
     138        boolean contains(Point2D p, State state) {
     139            final BasicStroke stroke = (BasicStroke) getContainer().getConnectionStroke();
     140            final double strokeWidth = stroke.getLineWidth();
     141           
     142            final Point2D closest = closest(line, p);
     143            return p.distance(closest) <= strokeWidth / 2;
     144        }
     145       
     146        @Override
     147        State click(State old) {
     148            end.extend(way);
     149            return new State.Invalid(old);
     150        }
     151       
     152        @Override
     153        Type getType() {
     154            return Type.EXTENDER;
     155        }
     156       
     157        @Override
     158        int getZIndex() {
     159            return 0;
     160        }
     161    }
     162   
     163    private final class LaneAdder extends InteractiveElement {
     164        private final Road.End end;
     165        private final Lane.Kind kind;
     166       
     167        private final Point2D center;
     168        private final Ellipse2D background;
     169       
     170        public LaneAdder(Road.End end, Lane.Kind kind) {
     171            this.end = end;
     172            this.kind = kind;
     173           
     174            final double a = getAngle(end) + PI;
     175            final Point2D lc = getLeftCorner(end);
     176            final Point2D rc = getRightCorner(end);
     177           
     178            final double r = connectorRadius;
     179            final double cx;
     180            final double cy;
     181            if (kind == Lane.Kind.EXTRA_LEFT) {
     182                final JunctionGui j = getContainer().getGui(end.getJunction());
     183                final Point2D i = intersection(line(j.getPoint(), a), new Line2D.Double(lc, rc));
     184               
     185                cx = i.getX() + 21d / 16 * r * (2 * cos(a) + cos(a - PI / 2));
     186                cy = i.getY() - 21d / 16 * r * (2 * sin(a) + sin(a - PI / 2));
     187            } else {
     188                cx = rc.getX() + 21d / 16 * r * (2 * cos(a) + cos(a + PI / 2));
     189                cy = rc.getY() - 21d / 16 * r * (2 * sin(a) + sin(a + PI / 2));
     190            }
     191           
     192            center = new Point2D.Double(cx, cy);
     193            background = new Ellipse2D.Double(cx - r, cy - r, 2 * r, 2 * r);
     194        }
     195       
     196        @Override
     197        void paint(Graphics2D g2d, State state) {
     198            if (!isVisible(state)) {
     199                return;
     200            }
     201           
     202            g2d.setColor(Color.DARK_GRAY);
     203            g2d.fill(background);
     204           
     205            final double l = 2 * connectorRadius / 3;
     206            final Line2D v = new Line2D.Double(center.getX(), center.getY() - l, center.getX(), center.getY() + l);
     207            final Line2D h = new Line2D.Double(center.getX() - l, center.getY(), center.getX() + l, center.getY());
     208           
     209            g2d.setStroke(new BasicStroke((float) (connectorRadius / 5)));
     210            g2d.setColor(Color.WHITE);
     211            g2d.draw(v);
     212            g2d.draw(h);
     213        }
     214       
     215        private boolean isVisible(State state) {
     216            return end.getJunction().isPrimary();
     217        }
     218       
     219        @Override
     220        boolean contains(Point2D p, State state) {
     221            return isVisible(state) && background.contains(p);
     222        }
     223       
     224        @Override
     225        Type getType() {
     226            return Type.LANE_ADDER;
     227        }
     228       
     229        @Override
     230        int getZIndex() {
     231            return 2;
     232        }
     233       
     234        @Override
     235        public State click(State old) {
     236            end.addLane(kind);
     237            return new State.Invalid(old);
     238        }
     239    }
     240   
     241    final class IncomingConnector extends InteractiveElement {
     242        private final Road.End end;
     243        private final List<LaneGui> lanes;
     244       
     245        private final Point2D center = new Point2D.Double();
     246        private final Ellipse2D circle = new Ellipse2D.Double();
     247       
     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);
     256        }
     257       
     258        @Override
     259        public void paintBackground(Graphics2D g2d, State state) {
     260            if (isActive(state)) {
     261                final Composite old = g2d.getComposite();
     262                g2d.setComposite(((AlphaComposite) old).derive(0.2f));
     263               
     264                g2d.setColor(new Color(255, 127, 31));
     265               
     266                for (LaneGui l : lanes) {
     267                    l.fill(g2d);
     268                }
     269               
     270                g2d.setComposite(old);
     271            }
     272        }
     273       
     274        @Override
     275        public void paint(Graphics2D g2d, State state) {
     276            if (isVisible(state)) {
     277                final Composite old = g2d.getComposite();
     278                if (isActive(state)) {
     279                    g2d.setComposite(((AlphaComposite) old).derive(1f));
     280                }
     281               
     282                g2d.setColor(Color.LIGHT_GRAY);
     283                g2d.fill(circle);
     284               
     285                g2d.setComposite(old);
     286            }
     287        }
     288       
     289        private boolean isActive(State state) {
     290            if (!(state instanceof State.IncomingActive)) {
     291                return false;
     292            }
     293           
     294            final Road.End roadEnd = ((State.IncomingActive) state).getRoadEnd();
     295           
     296            return roadEnd.equals(getRoadEnd());
     297        }
     298       
     299        private boolean isVisible(State state) {
     300            if (getModel().isPrimary() || !getRoadEnd().getJunction().isPrimary()
     301                || getRoadEnd().getOppositeEnd().getLanes().isEmpty()) {
     302                return false;
     303            }
     304           
     305            if (state instanceof State.Connecting) {
     306                return ((State.Connecting) state).getJunction().equals(getRoadEnd().getJunction());
     307            }
     308           
     309            return true;
     310        }
     311       
     312        @Override
     313        public boolean contains(Point2D p, State state) {
     314            if (!isVisible(state)) {
     315                return false;
     316            } else if (circle.contains(p)) {
     317                return true;
     318            }
     319           
     320            for (LaneGui l : lanes) {
     321                if (l.contains(p)) {
     322                    return true;
     323                }
     324            }
     325           
     326            return false;
     327        }
     328       
     329        @Override
     330        public Type getType() {
     331            return Type.INCOMING_CONNECTOR;
     332        }
     333       
     334        @Override
     335        public State activate(State old) {
     336            return new State.IncomingActive(getRoadEnd());
     337        }
     338       
     339        public Point2D getCenter() {
     340            return (Point2D) center.clone();
     341        }
     342       
     343        void move(double x, double y) {
     344            final double r = connectorRadius;
     345           
     346            center.setLocation(x, y);
     347            circle.setFrame(x - r, y - r, 2 * r, 2 * r);
     348        }
     349       
     350        public Road.End getRoadEnd() {
     351            return end;
     352        }
     353       
     354        public List<LaneGui> getLanes() {
     355            return lanes;
     356        }
     357       
     358        @Override
     359        int getZIndex() {
     360            return 1;
     361        }
     362       
     363        public void add(LaneGui lane) {
     364            lanes.add(lane);
     365        }
     366    }
     367   
     368    // TODO rework to be a SegmentGui (with getModel())
     369    private final class Segment {
     370        final Point2D to;
     371        final Point2D from;
     372       
     373        final Segment prev;
     374        final Segment next;
     375       
     376        final double length;
     377        final double angle;
     378       
     379        public Segment(Segment next, List<Point2D> bends, JunctionGui a) {
     380            final Point2D head = (Point2D) bends.get(0).clone();
     381            final List<Point2D> tail = bends.subList(1, bends.size());
     382           
     383            this.next = next;
     384            this.to = head;
     385            this.from = (Point2D) (tail.isEmpty() ? a.getPoint() : tail.get(0)).clone();
     386            this.prev = tail.isEmpty() ? null : new Segment(this, tail, a);
     387            this.length = from.distance(to);
     388            this.angle = angle(from, to);
     389           
     390            // TODO create a factory method for the segments list and pass it to
     391            // the constructor(s)
     392            segments.add(this);
     393        }
     394       
     395        public Segment(JunctionGui b, List<Point2D> bends, JunctionGui a) {
     396            this((Segment) null, prepended(bends, (Point2D) b.getPoint().clone()), a);
     397        }
     398       
     399        private double getFromOffset() {
     400            return prev == null ? 0 : prev.getFromOffset() + prev.length;
     401        }
     402       
     403        public double getOffset(double x, double y) {
     404            return getOffsetInternal(new Point2D.Double(x, y), -1, Double.POSITIVE_INFINITY);
     405        }
     406       
     407        private double getOffsetInternal(Point2D p, double offset, double quality) {
     408            final Point2D closest = closest(new Line2D.Double(from, to), p);
     409            final double myQuality = closest.distance(p);
     410           
     411            if (myQuality < quality) {
     412                quality = myQuality;
     413               
     414                final Line2D normal = line(p, angle + PI / 2);
     415                final Point2D isect = intersection(normal, new Line2D.Double(from, to));
     416                final double d = from.distance(isect);
     417                final boolean negative = Math.abs(angle(from, isect) - angle) > 1;
     418               
     419                offset = getFromOffset() + (negative ? -1 : 1) * d;
     420            }
     421           
     422            return next == null ? offset : next.getOffsetInternal(p, offset, quality);
     423        }
     424       
     425        public Path append(Path path, boolean forward, double offset) {
     426            if (ROUND_CORNERS) {
     427                final Segment n = forward ? prev : next;
     428                final Point2D s = forward ? to : from;
     429                final Point2D e = forward ? from : to;
     430               
     431                if (n == null) {
     432                    return path.lineTo(e.getX(), e.getY(), length - offset);
     433                }
     434               
     435                final double a = minAngleDiff(angle, n.angle);
     436                final double d = 3 * outerMargin + getWidth(getModel().getToEnd(), (forward && a < 0) || (!forward && a > 0));
     437                final double l = d * tan(abs(a));
     438               
     439                if (length - offset < l / 2 || n.length < l / 2) {
     440                    return n.append(path.lineTo(e.getX(), e.getY(), length - offset), forward, 0);
     441                } else {
     442                    final Point2D p = relativePoint(e, l / 2, angle(e, s));
     443                   
     444                    final Path line = path.lineTo(p.getX(), p.getY(), length - l / 2 - offset);
     445                    final Path curve = line.curveTo(d, d, a, l);
     446                   
     447                    return n.append(curve, forward, l / 2);
     448                }
     449            } else if (forward) {
     450                final Path tmp = path.lineTo(from.getX(), from.getY(), length);
     451                return prev == null ? tmp : prev.append(tmp, forward, 0);
     452            } else {
     453                final Path tmp = path.lineTo(to.getX(), to.getY(), length);
     454                return next == null ? tmp : next.append(tmp, forward, 0);
     455            }
     456        }
     457    }
     458   
     459    /**
     460    * This should become a setting, but rounding is (as of yet) still slightly buggy and a low
     461    * priority.
     462    */
     463    private static final boolean ROUND_CORNERS = false;
     464   
     465    private static final List<Point2D> prepended(List<Point2D> bends, Point2D point) {
     466        final List<Point2D> result = new ArrayList<Point2D>(bends.size() + 1);
     467        result.add(point);
     468        result.addAll(bends);
     469        return result;
     470    }
     471   
     472    private final GuiContainer container;
     473    private final double innerMargin;
     474    private final double outerMargin;
     475   
     476    private final float lineWidth;
     477    private final Stroke regularStroke;
     478    private final Stroke dashedStroke;
     479   
     480    private final JunctionGui a;
     481    private final JunctionGui b;
     482    private final double length;
     483   
     484    private final IncomingConnector incomingA;
     485    private final IncomingConnector incomingB;
     486   
     487    private final Road road;
     488    private final List<Segment> segments = new ArrayList<Segment>();
     489   
     490    final double connectorRadius;
     491   
     492    public RoadGui(GuiContainer container, Road road) {
     493        this.container = container;
     494       
     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());
     502       
     503        final List<Point2D> bends = new ArrayList<Point2D>();
     504        final List<Node> nodes = road.getRoute().getNodes();
     505        for (int i = nodes.size() - 2; i > 0; --i) {
     506            bends.add(container.translateAndScale(loc(nodes.get(i))));
     507        }
     508       
     509        // they add themselves to this.segments
     510        new Segment(b, bends, a);
     511        double l = 0;
     512        for (Segment s : segments) {
     513            l += s.length;
     514        }
     515        this.length = l;
     516       
     517        this.innerMargin = !incomingA.getLanes().isEmpty() && !incomingB.getLanes().isEmpty() ? 1 * container
     518            .getLaneWidth() / 15 : 0;
     519        this.outerMargin = container.getLaneWidth() / 6;
     520        this.connectorRadius = 3 * container.getLaneWidth() / 8;
     521        this.lineWidth = (float) (container.getLaneWidth() / 30);
     522        this.regularStroke = new BasicStroke(2 * lineWidth);
     523        this.dashedStroke = new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10f, new float[] {
     524            (float) (container.getLaneWidth() / 2), (float) (container.getLaneWidth() / 3)
     525        }, 0);
     526    }
     527   
     528    public JunctionGui getA() {
     529        return a;
     530    }
     531   
     532    public JunctionGui getB() {
     533        return b;
     534    }
     535   
     536    public Line2D getLeftCurb(Road.End end) {
     537        return GuiUtil.line(getCorner(end, true), getAngle(end) + PI);
     538    }
     539   
     540    public Line2D getRightCurb(Road.End end) {
     541        return GuiUtil.line(getCorner(end, false), getAngle(end) + PI);
     542    }
     543   
     544    private Point2D getLeftCorner(Road.End end) {
     545        return getCorner(end, true);
     546    }
     547   
     548    private Point2D getRightCorner(Road.End end) {
     549        return getCorner(end, false);
     550    }
     551   
     552    private Point2D getCorner(Road.End end, boolean left) {
     553        final JunctionGui j = end.isFromEnd() ? a : b;
     554        final double w = left ? getWidth(end, true) : getWidth(end, false);
     555        final double s = (left ? 1 : -1);
     556        final double a = getAngle(end) + PI;
     557        final double t = left ? j.getLeftTrim(end) : j.getRightTrim(end);
     558       
     559        final double dx = s * cos(PI / 2 - a) * w + cos(a) * t;
     560        final double dy = s * sin(PI / 2 - a) * w - sin(a) * t;
     561       
     562        return new Point2D.Double(j.x + dx, j.y + dy);
     563    }
     564   
     565    private double getWidth(Road.End end, boolean left) {
     566        if (!end.getRoad().equals(road)) {
     567            throw new IllegalArgumentException();
     568        }
     569       
     570        final int lcForward = incomingA.getLanes().size();
     571        final int lcBackward = incomingB.getLanes().size();
     572       
     573        final double LW = getContainer().getLaneWidth();
     574        final double M = innerMargin + outerMargin;
     575       
     576        if (end.isToEnd()) {
     577            return (left ? lcBackward : lcForward) * LW + M;
     578        } else {
     579            return (left ? lcForward : lcBackward) * LW + M;
     580        }
     581    }
     582   
     583    List<InteractiveElement> paint(Graphics2D g2d) {
     584        final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
     585       
     586        result.addAll(paintLanes(g2d));
     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        }
     596       
     597        g2d.setColor(Color.RED);
     598        for (Segment s : segments) {
     599            g2d.fill(new Ellipse2D.Double(s.from.getX() - 1, s.from.getY() - 1, 2, 2));
     600        }
     601       
     602        return result;
     603    }
     604   
     605    private List<LaneAdder> laneAdders() {
     606        final List<LaneAdder> result = new ArrayList<LaneAdder>(4);
     607       
     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));
     616        }
     617       
     618        return result;
     619    }
     620   
     621    private List<Extender> extenders(Road.End end) {
     622        if (!end.isExtendable()) {
     623            return Collections.emptyList();
     624        }
     625       
     626        final List<Extender> result = new ArrayList<Extender>();
     627       
     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)) {
     631                final Node nextNode = w.firstNode().equals(n) ? w.getNode(1) : w.getNode(w.getNodesCount() - 2);
     632                final Point2D nextNodeLoc = getContainer().translateAndScale(loc(nextNode));
     633                result.add(new Extender(end, w, angle(a.getPoint(), nextNodeLoc)));
     634            }
     635        }
     636       
     637        return result;
     638    }
     639   
     640    public Road getModel() {
     641        return road;
     642    }
     643   
     644    public IncomingConnector getConnector(Road.End end) {
     645        return end.isFromEnd() ? incomingA : incomingB;
     646    }
     647   
     648    private List<InteractiveElement> paintLanes(Graphics2D g2d) {
     649        final Path2D middleLines = new Path2D.Double();
     650       
     651        g2d.setStroke(regularStroke);
     652       
     653        final boolean forward = !incomingA.getLanes().isEmpty();
     654        final boolean backward = !incomingB.getLanes().isEmpty();
     655       
     656        final Path2D middleArea;
     657        if (forward && backward) {
     658            paintLanes(g2d, middleLines, true);
     659            paintLanes(g2d, middleLines, false);
     660           
     661            middleLines.closePath();
     662            middleArea = middleLines;
     663            g2d.setColor(new Color(160, 160, 160));
     664        } else if (forward || backward) {
     665            paintLanes(g2d, middleLines, forward);
     666           
     667            middleArea = new Path2D.Double();
     668            middleArea.append(middleLines.getPathIterator(null), false);
     669            middleArea.append(middlePath(backward).offset(outerMargin, -1, -1, outerMargin).getIterator(), true);
     670            middleArea.closePath();
     671            g2d.setColor(Color.GRAY);
     672        } else {
     673            throw new AssertionError();
     674        }
     675       
     676        g2d.fill(middleArea);
     677        g2d.setColor(Color.WHITE);
     678        g2d.draw(middleLines);
     679       
     680        final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
     681       
     682        moveIncoming(getModel().getFromEnd());
     683        moveIncoming(getModel().getToEnd());
     684        result.add(incomingA);
     685        result.add(incomingB);
     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                }
     696            }
     697        }
     698       
     699        return result;
     700    }
     701   
     702    private void paintLanes(Graphics2D g2d, Path2D middleLines, boolean forward) {
     703        final Shape clip = clip();
     704        g2d.clip(clip);
     705       
     706        final Path middle = middlePath(forward);
     707       
     708        Path innerPath = middle.offset(innerMargin, -1, -1, innerMargin);
     709        final List<Path> linePaths = new ArrayList<Path>();
     710        linePaths.add(innerPath);
     711       
     712        for (LaneGui l : forward ? incomingA.getLanes() : incomingB.getLanes()) {
     713            l.setClip(clip);
     714            innerPath = l.recalculate(innerPath, middleLines);
     715            linePaths.add(innerPath);
     716        }
     717       
     718        final Path2D area = new Path2D.Double();
     719        area(area, middle, innerPath.offset(outerMargin, -1, -1, outerMargin));
     720        g2d.setColor(Color.GRAY);
     721        g2d.fill(area);
     722       
     723        g2d.setColor(Color.WHITE);
     724        final Path2D lines = new Path2D.Double();
     725        lines.append(innerPath.getIterator(), false);
     726        g2d.draw(lines);
     727       
     728        // g2d.setColor(new Color(32, 128, 192));
     729        g2d.setColor(Color.WHITE);
     730        g2d.setStroke(dashedStroke);
     731        for (Path p : linePaths) {
     732            lines.reset();
     733            lines.append(p.getIterator(), false);
     734            g2d.draw(lines);
     735        }
     736        g2d.setStroke(regularStroke);
     737       
     738        // g2d.setColor(new Color(32, 128, 192));
     739        // lines.reset();
     740        // lines.append(middle.getIterator(), false);
     741        // g2d.draw(lines);
     742       
     743        g2d.setClip(null);
     744    }
     745   
     746    private Shape clip() {
     747        final Area clip = new Area(new Rectangle2D.Double(-100000, -100000, 200000, 200000));
     748        clip.subtract(new Area(negativeClip(true)));
     749        clip.subtract(new Area(negativeClip(false)));
     750       
     751        return clip;
     752    }
     753   
     754    private Shape negativeClip(boolean forward) {
     755        final Road.End end = forward ? getModel().getToEnd() : getModel().getFromEnd();
     756        final JunctionGui j = forward ? b : a;
     757       
     758        final Line2D lc = getLeftCurb(end);
     759        final Line2D rc = getRightCurb(end);
     760       
     761        final Path2D negativeClip = new Path2D.Double();
     762       
     763        final double d = rc.getP1().distance(j.getPoint()) + lc.getP1().distance(j.getPoint());
     764       
     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);
     772       
     773        negativeClip.moveTo(r1.getX(), r1.getY());
     774        negativeClip.lineTo(r2.getX(), r2.getY());
     775        negativeClip.lineTo(l2.getX(), l2.getY());
     776        negativeClip.lineTo(l1.getX(), l1.getY());
     777        negativeClip.closePath();
     778       
     779        return negativeClip;
     780    }
     781   
     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;
     788    }
     789   
     790    private Path middlePath(boolean forward) {
     791        final Path path = forward ? Path.create(b.x, b.y) : Path.create(a.x, a.y);
     792        final Segment first = forward ? segments.get(segments.size() - 1) : segments.get(0);
     793       
     794        return first.append(path, forward, 0);
     795    }
     796   
     797    private void moveIncoming(Road.End end) {
     798        final Point2D lc = getLeftCorner(end);
     799        final Point2D rc = getRightCorner(end);
     800        final Line2D cornerLine = new Line2D.Double(lc, rc);
     801       
     802        final double a = getAngle(end);
     803        final Line2D roadLine = line(getContainer().getGui(end.getJunction()).getPoint(), a);
     804       
     805        final Point2D i = intersection(roadLine, cornerLine);
     806        // TODO fix depending on angle(i, lc)
     807        final double offset = innerMargin + (getWidth(end, true) - innerMargin - outerMargin) / 2;
     808        final Point2D loc = relativePoint(i, offset, angle(i, lc));
     809       
     810        getConnector(end).move(loc.getX(), loc.getY());
     811    }
     812   
     813    private void moveOutgoing(LaneGui lane, int offset) {
     814        final Road.End end = lane.getModel().getOutgoingRoadEnd();
     815       
     816        final Point2D lc = getLeftCorner(end);
     817        final Point2D rc = getRightCorner(end);
     818        final Line2D cornerLine = new Line2D.Double(lc, rc);
     819       
     820        final double a = getAngle(end);
     821        final Line2D roadLine = line(getContainer().getGui(end.getJunction()).getPoint(), a);
     822       
     823        final Point2D i = intersection(roadLine, cornerLine);
     824        // TODO fix depending on angle(i, rc)
     825        final double d = innerMargin + (2 * offset + 1) * getContainer().getLaneWidth() / 2;
     826        final Point2D loc = relativePoint(i, d, angle(i, rc));
     827       
     828        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();
     837    }
     838   
     839    public double getAngle(Road.End end) {
     840        if (!getModel().equals(end.getRoad())) {
     841            throw new IllegalArgumentException();
     842        }
     843       
     844        if (end.isToEnd()) {
     845            return segments.get(segments.size() - 1).angle;
     846        } else {
     847            final double angle = segments.get(0).angle;
     848            return angle > PI ? angle - PI : angle + PI;
     849        }
     850    }
     851   
     852    public double getWidth(Road.End end) {
     853        return getWidth(end, true) + getWidth(end, false);
     854    }
     855   
     856    public double getLength() {
     857        return length;
     858    }
     859   
     860    public double getOffset(double x, double y) {
     861        return segments.get(0).getOffset(x, y);
     862    }
     863   
     864    public GuiContainer getContainer() {
     865        return container;
     866    }
     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    }
    880880}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/State.java

    r25783 r26154  
    1212
    1313interface 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        
    93         public class Invalid implements State {
    94                 private final State wrapped;
    95                
    96                 public Invalid(State wrapped) {
    97                         this.wrapped = wrapped;
    98                 }
    99                
    100                 public State unwrap() {
    101                         return wrapped;
    102                 }
    103         }
    104        
    105         public class Dirty implements State {
    106                 private final State wrapped;
    107                
    108                 public Dirty(State wrapped) {
    109                         this.wrapped = wrapped;
    110                 }
    111                
    112                 public State unwrap() {
    113                         return wrapped;
    114                 }
    115         }
    116        
    117         class Default implements State {
    118                 public Default() {}
    119         }
    120        
    121         class IncomingActive implements State {
    122                 private final Road.End roadEnd;
    123                
    124                 public IncomingActive(Road.End roadEnd) {
    125                         this.roadEnd = roadEnd;
    126                 }
    127                
    128                 public Road.End getRoadEnd() {
    129                         return roadEnd;
    130                 }
    131         }
    132        
    133         class OutgoingActive implements State {
    134                 private final LaneGui lane;
    135                
    136                 public OutgoingActive(LaneGui lane) {
    137                         this.lane = lane;
    138                 }
    139                
    140                 public LaneGui getLane() {
    141                         return lane;
    142                 }
    143         }
     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   
     93    public class Invalid implements State {
     94        private final State wrapped;
     95       
     96        public Invalid(State wrapped) {
     97            this.wrapped = wrapped;
     98        }
     99       
     100        public State unwrap() {
     101            return wrapped;
     102        }
     103    }
     104   
     105    public class Dirty implements State {
     106        private final State wrapped;
     107       
     108        public Dirty(State wrapped) {
     109            this.wrapped = wrapped;
     110        }
     111       
     112        public State unwrap() {
     113            return wrapped;
     114        }
     115    }
     116   
     117    class Default implements State {
     118        public Default() {}
     119    }
     120   
     121    class IncomingActive implements State {
     122        private final Road.End roadEnd;
     123       
     124        public IncomingActive(Road.End roadEnd) {
     125            this.roadEnd = roadEnd;
     126        }
     127       
     128        public Road.End getRoadEnd() {
     129            return roadEnd;
     130        }
     131    }
     132   
     133    class OutgoingActive implements State {
     134        private final LaneGui lane;
     135       
     136        public OutgoingActive(LaneGui lane) {
     137            this.lane = lane;
     138        }
     139       
     140        public LaneGui getLane() {
     141            return lane;
     142        }
     143    }
    144144}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/TurnLanesDialog.java

    r25783 r26154  
    1818
    1919import org.openstreetmap.josm.actions.JosmAction;
     20import org.openstreetmap.josm.Main;
    2021import org.openstreetmap.josm.data.SelectionChangedListener;
    2122import org.openstreetmap.josm.data.osm.DataSet;
     
    2728
    2829public class TurnLanesDialog extends ToggleDialog {
    29         private final Action editAction = new JosmAction(tr("Edit"), "dialogs/edit",
    30             tr("Edit turn relations and lane lengths for selected node."), null, true) {
    31                
    32                 private static final long serialVersionUID = 4114119073563457706L;
    33                
    34                 @Override
    35                 public void actionPerformed(ActionEvent e) {
    36                         final CardLayout cl = (CardLayout) body.getLayout();
    37                         cl.show(body, CARD_EDIT);
    38                         editing = true;
    39                 }
    40         };
    41         private final Action validateAction = new JosmAction(tr("Validate"), "dialogs/validator",
    42             tr("Validate turn- and lane-length-relations for consistency."), null, true) {
    43                
    44                 private static final long serialVersionUID = 7510740945725851427L;
    45                
    46                 @Override
    47                 public void actionPerformed(ActionEvent e) {
    48                         final CardLayout cl = (CardLayout) body.getLayout();
    49                         cl.show(body, CARD_VALIDATE);
    50                         editing = false;
    51                 }
    52         };
    53        
    54         private static final long serialVersionUID = -1998375221636611358L;
    55        
    56         private static final String CARD_EDIT = "EDIT";
    57         private static final String CARD_VALIDATE = "VALIDATE";
    58         private static final String CARD_ERROR = "ERROR";
    59        
    60         private final JPanel body = new JPanel();
    61         private final JunctionPane junctionPane = new JunctionPane(null);
    62         private final JLabel error = new JLabel();
    63        
    64         private boolean editing = true;
    65        
    66         public TurnLanesDialog() {
    67                 super(tr("Turn Lanes"), "turnlanes.png", tr("Edit turn lanes"), null, 200);
    68                
    69                 DataSet.addSelectionListener(new SelectionChangedListener() {
    70                         @Override
    71                         public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
    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);
    78                                         return;
    79                                 }
    80                                
    81                                 try {
    82                                         setJunction(ModelContainer.create(nodes, ways));
    83                                 } catch (RuntimeException e) {
    84                                         displayError(e);
    85                                         return;
    86                                 }
    87                         }
    88                 });
    89                
    90                 final JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 4, 4));
    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);
    98                
    99                 body.setLayout(new CardLayout(4, 4));
    100                
    101                 add(buttonPanel, BorderLayout.SOUTH);
    102                 add(body, BorderLayout.CENTER);
    103                
    104                 body.add(junctionPane, CARD_EDIT);
    105                 body.add(new ValidationPanel(), CARD_VALIDATE);
    106                 body.add(error, CARD_ERROR);
    107                
    108                 editButton.doClick();
    109         }
    110        
    111         void displayError(RuntimeException e) {
    112                 if (editing) {
    113                         e.printStackTrace();
    114                        
    115                         error.setText("<html>An error occured while constructing the model."
    116                             + " Please run the validator to make sure the data is consistent.<br><br>Error: " + e.getMessage()
    117                             + "</html>");
    118                        
    119                         final CardLayout cl = (CardLayout) body.getLayout();
    120                         cl.show(body, CARD_ERROR);
    121                 }
    122         }
    123        
    124         void setJunction(ModelContainer mc) {
    125                 if (mc != null && editing) {
    126                         junctionPane.setJunction(new GuiContainer(mc));
    127                         final CardLayout cl = (CardLayout) body.getLayout();
    128                         cl.show(body, CARD_EDIT);
    129                 }
    130         }
     30    private class EditAction extends JosmAction {
     31        private static final long serialVersionUID = 4114119073563457706L;
     32        public EditAction() {
     33            super(tr("Edit"), "dialogs/edit",
     34            tr("Edit turn relations and lane lengths for selected node."), null, false);
     35            putValue("toolbar", "turnlanes/edit");
     36            Main.toolbar.register(this);
     37        }
     38
     39        @Override
     40        public void actionPerformed(ActionEvent e) {
     41            final CardLayout cl = (CardLayout) body.getLayout();
     42            cl.show(body, CARD_EDIT);
     43            editing = true;
     44        }
     45    }
     46
     47    private class ValidateAction extends JosmAction {
     48        private static final long serialVersionUID = 7510740945725851427L;
     49        public ValidateAction() {
     50            super(tr("Validate"), "dialogs/validator",
     51            tr("Validate turn- and lane-length-relations for consistency."), null, false);
     52            putValue("toolbar", "turnlanes/validate");
     53            Main.toolbar.register(this);
     54        }
     55
     56        @Override
     57        public void actionPerformed(ActionEvent e) {
     58            final CardLayout cl = (CardLayout) body.getLayout();
     59            cl.show(body, CARD_VALIDATE);
     60            editing = false;
     61        }
     62    }
     63
     64    private final Action editAction = new EditAction();
     65    private final Action validateAction = new ValidateAction();
     66
     67    private static final long serialVersionUID = -1998375221636611358L;
     68
     69    private static final String CARD_EDIT = "EDIT";
     70    private static final String CARD_VALIDATE = "VALIDATE";
     71    private static final String CARD_ERROR = "ERROR";
     72
     73    private final JPanel body = new JPanel();
     74    private final JunctionPane junctionPane = new JunctionPane(null);
     75    private final JLabel error = new JLabel();
     76
     77    private boolean editing = true;
     78
     79    public TurnLanesDialog() {
     80        super(tr("Turn Lanes"), "turnlanes.png", tr("Edit turn lanes"), null, 200);
     81
     82        DataSet.addSelectionListener(new SelectionChangedListener() {
     83            @Override
     84            public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
     85                final Collection<OsmPrimitive> s = Collections.unmodifiableCollection(newSelection);
     86                final List<Node> nodes = OsmPrimitive.getFilteredList(s, Node.class);
     87                final List<Way> ways = OsmPrimitive.getFilteredList(s, Way.class);
     88
     89                if (nodes.isEmpty()) {
     90                    setJunction(null);
     91                    return;
     92                }
     93
     94                try {
     95                    setJunction(ModelContainer.create(nodes, ways));
     96                } catch (RuntimeException e) {
     97                    displayError(e);
     98                    return;
     99                }
     100            }
     101        });
     102
     103        final JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 4, 4));
     104        final ButtonGroup group = new ButtonGroup();
     105        final JToggleButton editButton = new JToggleButton(editAction);
     106        final JToggleButton validateButton = new JToggleButton(validateAction);
     107        group.add(editButton);
     108        group.add(validateButton);
     109        buttonPanel.add(editButton);
     110        buttonPanel.add(validateButton);
     111
     112        body.setLayout(new CardLayout(4, 4));
     113
     114        add(buttonPanel, BorderLayout.SOUTH);
     115        add(body, BorderLayout.CENTER);
     116
     117        body.add(junctionPane, CARD_EDIT);
     118        body.add(new ValidationPanel(), CARD_VALIDATE);
     119        body.add(error, CARD_ERROR);
     120
     121        editButton.doClick();
     122    }
     123
     124    void displayError(RuntimeException e) {
     125        if (editing) {
     126            e.printStackTrace();
     127
     128            error.setText("<html>An error occured while constructing the model."
     129                + " Please run the validator to make sure the data is consistent.<br><br>Error: " + e.getMessage()
     130                + "</html>");
     131
     132            final CardLayout cl = (CardLayout) body.getLayout();
     133            cl.show(body, CARD_ERROR);
     134        }
     135    }
     136
     137    void setJunction(ModelContainer mc) {
     138        if (mc != null && editing) {
     139            junctionPane.setJunction(new GuiContainer(mc));
     140            final CardLayout cl = (CardLayout) body.getLayout();
     141            cl.show(body, CARD_EDIT);
     142        }
     143    }
    131144}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ValidationPanel.java

    r25606 r26154  
    2525
    2626class ValidationPanel extends JPanel {
    27         private static final long serialVersionUID = -1585778734201458665L;
    28        
    29         private static final String[] COLUMN_NAMES = {
    30             tr("Description"), tr("Type"), tr("Quick-Fix")
    31         };
    32        
    33         private final Action refreshAction = new JosmAction(tr("Refresh"), "dialogs/refresh",
    34             tr("Revalidate all turnlanes-relations."), null, false) {
    35                 private static final long serialVersionUID = -8110599654128234810L;
    36                
    37                 @Override
    38                 public void actionPerformed(ActionEvent e) {
    39                         setIssues(new Validator().validate(Main.main.getCurrentDataSet()));
    40                 }
    41         };
    42        
    43         private final Action fixAction = new JosmAction(tr("Fix"), "dialogs/fix", tr("Automatically fixes the issue."), null,
    44             false) {
    45                 private static final long serialVersionUID = -8110599654128234810L;
    46                
    47                 @Override
    48                 public void actionPerformed(ActionEvent e) {
    49                         if (selected.getQuickFix().perform()) {
    50                                 final int i = issues.indexOf(selected);
    51                                 issueModel.removeRow(i);
    52                                 issues.remove(i);
    53                         }
    54                 }
    55         };
    56        
    57         private final Action selectAction = new JosmAction(tr("Select"), "dialogs/select",
    58             tr("Selects the offending relation."), null, false) {
    59                 private static final long serialVersionUID = -8110599654128234810L;
    60                
    61                 @Override
    62                 public void actionPerformed(ActionEvent e) {
    63                         if (selected.getRelation() == null) {
    64                                 Main.main.getCurrentDataSet().setSelected(selected.getPrimitives());
    65                         } else {
    66                                 Main.main.getCurrentDataSet().setSelected(selected.getRelation());
    67                         }
    68                 }
    69         };
    70        
    71         private final SideButton refreshButton = new SideButton(refreshAction);
    72         private final SideButton fixButton = new SideButton(fixAction);
    73         private final SideButton selectButton = new SideButton(selectAction);
    74        
    75         private final DefaultTableModel issueModel = new DefaultTableModel(COLUMN_NAMES, 0);
    76         private final List<Issue> issues = new ArrayList<Issue>();
    77         private final JTable issueTable = new JTable(issueModel) {
    78                 private static final long serialVersionUID = 6323348290180585298L;
    79                
    80                 public boolean isCellEditable(int row, int column) {
    81                         return false;
    82                 };
    83         };
    84        
    85         private Issue selected;
    86        
    87         public ValidationPanel() {
    88                 super(new BorderLayout(4, 4));
    89                
    90                 final JPanel buttonPanel = new JPanel(new GridLayout(1, 3, 4, 4));
    91                
    92                 buttonPanel.add(refreshButton);
    93                 buttonPanel.add(fixButton);
    94                 buttonPanel.add(selectButton);
    95                
    96                 add(buttonPanel, BorderLayout.NORTH);
    97                 add(new JScrollPane(issueTable), BorderLayout.CENTER);
    98                
    99                 issueTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    100                 issueTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
    101                         @Override
    102                         public void valueChanged(ListSelectionEvent e) {
    103                                 final int i = issueTable.getSelectedRow();
    104                                 final Issue issue = i >= 0 ? issues.get(i) : null;
    105                                
    106                                 setSelected(issue);
    107                         }
    108                 });
    109                
    110                 setSelected(null);
    111         }
    112        
    113         private void setIssues(List<Issue> issues) {
    114                 issueModel.setRowCount(0);
    115                 this.issues.clear();
    116                
    117                 for (Issue i : issues) {
    118                         final String[] row = {
    119                             i.getDescription(), //
    120                             i.getRelation() == null ? tr("(none)") : i.getRelation().get("type"), //
    121                             i.getQuickFix().getDescription()
    122                         };
    123                         issueModel.addRow(row);
    124                         this.issues.add(i);
    125                 }
    126         }
    127        
    128         private void setSelected(Issue selected) {
    129                 this.selected = selected;
    130                
    131                 if (selected == null) {
    132                         fixButton.setEnabled(false);
    133                         selectButton.setEnabled(false);
    134                 } else {
    135                         fixButton.setEnabled(selected.getQuickFix() != Issue.QuickFix.NONE);
    136                         selectButton.setEnabled(true);
    137                 }
    138         }
     27    private static final long serialVersionUID = -1585778734201458665L;
     28   
     29    private static final String[] COLUMN_NAMES = {
     30        tr("Description"), tr("Type"), tr("Quick-Fix")
     31    };
     32   
     33    private final Action refreshAction = new JosmAction(tr("Refresh"), "dialogs/refresh",
     34        tr("Revalidate all turnlanes-relations."), null, false) {
     35        private static final long serialVersionUID = -8110599654128234810L;
     36       
     37        @Override
     38        public void actionPerformed(ActionEvent e) {
     39            setIssues(new Validator().validate(Main.main.getCurrentDataSet()));
     40        }
     41    };
     42   
     43    private final Action fixAction = new JosmAction(tr("Fix"), "dialogs/fix", tr("Automatically fixes the issue."), null,
     44        false) {
     45        private static final long serialVersionUID = -8110599654128234810L;
     46       
     47        @Override
     48        public void actionPerformed(ActionEvent e) {
     49            if (selected.getQuickFix().perform()) {
     50                final int i = issues.indexOf(selected);
     51                issueModel.removeRow(i);
     52                issues.remove(i);
     53            }
     54        }
     55    };
     56   
     57    private final Action selectAction = new JosmAction(tr("Select"), "dialogs/select",
     58        tr("Selects the offending relation."), null, false) {
     59        private static final long serialVersionUID = -8110599654128234810L;
     60       
     61        @Override
     62        public void actionPerformed(ActionEvent e) {
     63            if (selected.getRelation() == null) {
     64                Main.main.getCurrentDataSet().setSelected(selected.getPrimitives());
     65            } else {
     66                Main.main.getCurrentDataSet().setSelected(selected.getRelation());
     67            }
     68        }
     69    };
     70   
     71    private final SideButton refreshButton = new SideButton(refreshAction);
     72    private final SideButton fixButton = new SideButton(fixAction);
     73    private final SideButton selectButton = new SideButton(selectAction);
     74   
     75    private final DefaultTableModel issueModel = new DefaultTableModel(COLUMN_NAMES, 0);
     76    private final List<Issue> issues = new ArrayList<Issue>();
     77    private final JTable issueTable = new JTable(issueModel) {
     78        private static final long serialVersionUID = 6323348290180585298L;
     79       
     80        public boolean isCellEditable(int row, int column) {
     81            return false;
     82        };
     83    };
     84   
     85    private Issue selected;
     86   
     87    public ValidationPanel() {
     88        super(new BorderLayout(4, 4));
     89       
     90        final JPanel buttonPanel = new JPanel(new GridLayout(1, 3, 4, 4));
     91       
     92        buttonPanel.add(refreshButton);
     93        buttonPanel.add(fixButton);
     94        buttonPanel.add(selectButton);
     95       
     96        add(buttonPanel, BorderLayout.NORTH);
     97        add(new JScrollPane(issueTable), BorderLayout.CENTER);
     98       
     99        issueTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
     100        issueTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
     101            @Override
     102            public void valueChanged(ListSelectionEvent e) {
     103                final int i = issueTable.getSelectedRow();
     104                final Issue issue = i >= 0 ? issues.get(i) : null;
     105               
     106                setSelected(issue);
     107            }
     108        });
     109       
     110        setSelected(null);
     111    }
     112   
     113    private void setIssues(List<Issue> issues) {
     114        issueModel.setRowCount(0);
     115        this.issues.clear();
     116       
     117        for (Issue i : issues) {
     118            final String[] row = {
     119                i.getDescription(), //
     120                i.getRelation() == null ? tr("(none)") : i.getRelation().get("type"), //
     121                i.getQuickFix().getDescription()
     122            };
     123            issueModel.addRow(row);
     124            this.issues.add(i);
     125        }
     126    }
     127   
     128    private void setSelected(Issue selected) {
     129        this.selected = selected;
     130       
     131        if (selected == null) {
     132            fixButton.setEnabled(false);
     133            selectButton.setEnabled(false);
     134        } else {
     135            fixButton.setEnabled(selected.getQuickFix() != Issue.QuickFix.NONE);
     136            selectButton.setEnabled(true);
     137        }
     138    }
    139139}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Constants.java

    r25783 r26154  
    44
    55public interface Constants {
    6         String SEPARATOR = ";";
    7         String SPLIT_REGEX = "\\p{Zs}*[,:;]\\p{Zs}*";
    8         Pattern SPLIT_PATTERN = Pattern.compile(SPLIT_REGEX);
    9        
    10         String TYPE_LENGTHS = "turnlanes:lengths";
    11        
    12         String LENGTHS_KEY_LENGTHS_LEFT = "lengths:left";
    13         String LENGTHS_KEY_LENGTHS_RIGHT = "lengths:right";
    14        
    15         String TYPE_TURNS = "turnlanes:turns";
    16        
    17         String TURN_ROLE_VIA = "via";
    18         String TURN_ROLE_FROM = "from";
    19         String TURN_ROLE_TO = "to";
    20        
    21         String TURN_KEY_LANES = "lanes";
    22         String TURN_KEY_EXTRA_LANES = "lanes:extra";
    23         String LENGTHS_ROLE_END = "end";
    24         String LENGTHS_ROLE_WAYS = "ways";
    25        
     6    String SEPARATOR = ";";
     7    String SPLIT_REGEX = "\\p{Zs}*[,:;]\\p{Zs}*";
     8    Pattern SPLIT_PATTERN = Pattern.compile(SPLIT_REGEX);
     9   
     10    String TYPE_LENGTHS = "turnlanes:lengths";
     11   
     12    String LENGTHS_KEY_LENGTHS_LEFT = "lengths:left";
     13    String LENGTHS_KEY_LENGTHS_RIGHT = "lengths:right";
     14   
     15    String TYPE_TURNS = "turnlanes:turns";
     16   
     17    String TURN_ROLE_VIA = "via";
     18    String TURN_ROLE_FROM = "from";
     19    String TURN_ROLE_TO = "to";
     20   
     21    String TURN_KEY_LANES = "lanes";
     22    String TURN_KEY_EXTRA_LANES = "lanes:extra";
     23    String LENGTHS_ROLE_END = "end";
     24    String LENGTHS_ROLE_WAYS = "ways";
     25   
    2626}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Issue.java

    r25606 r26154  
    1212
    1313public class Issue {
    14         public enum Severity {
    15                 INFO,
    16                 WARN,
    17                 ERROR;
    18         }
    19        
    20         public static abstract class QuickFix {
    21                 public static final QuickFix NONE = new QuickFix(tr("None")) {
    22                        
    23                         @Override
    24                         public boolean perform() {
    25                                 throw new UnsupportedOperationException("Don't call perform on Issue.QuickFix.NONE.");
    26                         }
    27                 };
    28                
    29                 private final String description;
    30                
    31                 public QuickFix(String description) {
    32                         this.description = description;
    33                 }
    34                
    35                 public String getDescription() {
    36                         return description;
    37                 }
    38                
    39                 public abstract boolean perform();
    40         }
    41        
    42         private final Severity severity;
    43         private final Relation relation;
    44         private final List<OsmPrimitive> primitives;
    45         private final String description;
    46         private final QuickFix quickFix;
    47        
    48         private Issue(Severity severity, Relation relation, List<? extends OsmPrimitive> primitives, String description,
    49             QuickFix quickFix) {
    50                 this.relation = relation;
    51                 this.primitives = Collections.unmodifiableList(new ArrayList<OsmPrimitive>(primitives));
    52                 this.severity = severity;
    53                 this.description = description;
    54                 this.quickFix = quickFix;
    55         }
    56        
    57         public static Issue newError(Relation relation, List<? extends OsmPrimitive> primitives, String description,
    58             QuickFix quickFix) {
    59                 return new Issue(Severity.ERROR, relation, primitives, description, quickFix);
    60         }
    61        
    62         public static Issue newError(Relation relation, List<? extends OsmPrimitive> primitives, String description) {
    63                 return newError(relation, primitives, description, QuickFix.NONE);
    64         }
    65        
    66         public static Issue newError(Relation relation, OsmPrimitive primitive, String description) {
    67                 return newError(relation, Arrays.asList(primitive), description, QuickFix.NONE);
    68         }
    69        
    70         public static Issue newError(Relation relation, String description) {
    71                 return newError(relation, Collections.<OsmPrimitive> emptyList(), description, QuickFix.NONE);
    72         }
    73        
    74         public static Issue newWarning(List<OsmPrimitive> primitives, String description) {
    75                 return new Issue(Severity.WARN, null, primitives, description, QuickFix.NONE);
    76         }
    77        
    78         public Severity getSeverity() {
    79                 return severity;
    80         }
    81        
    82         public String getDescription() {
    83                 return description;
    84         }
    85        
    86         public Relation getRelation() {
    87                 return relation;
    88         }
    89        
    90         public List<OsmPrimitive> getPrimitives() {
    91                 return primitives;
    92         }
    93        
    94         public QuickFix getQuickFix() {
    95                 return quickFix;
    96         }
     14    public enum Severity {
     15        INFO,
     16        WARN,
     17        ERROR;
     18    }
     19   
     20    public static abstract class QuickFix {
     21        public static final QuickFix NONE = new QuickFix(tr("None")) {
     22           
     23            @Override
     24            public boolean perform() {
     25                throw new UnsupportedOperationException("Don't call perform on Issue.QuickFix.NONE.");
     26            }
     27        };
     28       
     29        private final String description;
     30       
     31        public QuickFix(String description) {
     32            this.description = description;
     33        }
     34       
     35        public String getDescription() {
     36            return description;
     37        }
     38       
     39        public abstract boolean perform();
     40    }
     41   
     42    private final Severity severity;
     43    private final Relation relation;
     44    private final List<OsmPrimitive> primitives;
     45    private final String description;
     46    private final QuickFix quickFix;
     47   
     48    private Issue(Severity severity, Relation relation, List<? extends OsmPrimitive> primitives, String description,
     49        QuickFix quickFix) {
     50        this.relation = relation;
     51        this.primitives = Collections.unmodifiableList(new ArrayList<OsmPrimitive>(primitives));
     52        this.severity = severity;
     53        this.description = description;
     54        this.quickFix = quickFix;
     55    }
     56   
     57    public static Issue newError(Relation relation, List<? extends OsmPrimitive> primitives, String description,
     58        QuickFix quickFix) {
     59        return new Issue(Severity.ERROR, relation, primitives, description, quickFix);
     60    }
     61   
     62    public static Issue newError(Relation relation, List<? extends OsmPrimitive> primitives, String description) {
     63        return newError(relation, primitives, description, QuickFix.NONE);
     64    }
     65   
     66    public static Issue newError(Relation relation, OsmPrimitive primitive, String description) {
     67        return newError(relation, Arrays.asList(primitive), description, QuickFix.NONE);
     68    }
     69   
     70    public static Issue newError(Relation relation, String description) {
     71        return newError(relation, Collections.<OsmPrimitive> emptyList(), description, QuickFix.NONE);
     72    }
     73   
     74    public static Issue newWarning(List<OsmPrimitive> primitives, String description) {
     75        return new Issue(Severity.WARN, null, primitives, description, QuickFix.NONE);
     76    }
     77   
     78    public Severity getSeverity() {
     79        return severity;
     80    }
     81   
     82    public String getDescription() {
     83        return description;
     84    }
     85   
     86    public Relation getRelation() {
     87        return relation;
     88    }
     89   
     90    public List<OsmPrimitive> getPrimitives() {
     91        return primitives;
     92    }
     93   
     94    public QuickFix getQuickFix() {
     95        return quickFix;
     96    }
    9797}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Junction.java

    r25783 r26154  
    1010
    1111public class Junction {
    12         private final ModelContainer container;
    13        
    14         private final Node node;
    15         private final Set<Way> roads = new HashSet<Way>();
    16        
    17         Junction(ModelContainer container, Node n) {
    18                 this.container = container;
    19                 this.node = n;
    20                
    21                 container.register(this);
    22                
    23                 if (isPrimary()) {
    24                         // if turn data is invalid, this will force an exception now, not later during painting
    25                         // getTurns(); TODO force this again
    26                 }
    27         }
    28        
    29         public boolean isPrimary() {
    30                 return getContainer().isPrimary(this);
    31         }
    32        
    33         public Node getNode() {
    34                 return node;
    35         }
    36        
    37         public List<Road> getRoads() {
    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;
    45         }
    46        
    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;
    55         }
    56        
    57         void addRoad(Way w) {
    58                 roads.add(w);
    59         }
    60        
    61         Road.End getRoadEnd(Way w) {
    62                 final Road r = getContainer().getRoad(w);
    63                
    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();
    74                         }
    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();
    79                 }
    80                
    81                 throw new IllegalArgumentException("While there exists a road for the given way, the way neither "
    82                     + "starts nor ends at the junction node.");
    83         }
    84        
    85         public ModelContainer getContainer() {
    86                 return container;
    87         }
     12    private final ModelContainer container;
     13   
     14    private final Node node;
     15    private final Set<Way> roads = new HashSet<Way>();
     16   
     17    Junction(ModelContainer container, Node n) {
     18        this.container = container;
     19        this.node = n;
     20       
     21        container.register(this);
     22       
     23        if (isPrimary()) {
     24            // if turn data is invalid, this will force an exception now, not later during painting
     25            // getTurns(); TODO force this again
     26        }
     27    }
     28   
     29    public boolean isPrimary() {
     30        return getContainer().isPrimary(this);
     31    }
     32   
     33    public Node getNode() {
     34        return node;
     35    }
     36   
     37    public List<Road> getRoads() {
     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;
     45    }
     46   
     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;
     55    }
     56   
     57    void addRoad(Way w) {
     58        roads.add(w);
     59    }
     60   
     61    Road.End getRoadEnd(Way w) {
     62        final Road r = getContainer().getRoad(w);
     63       
     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();
     74            }
     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();
     79        }
     80       
     81        throw new IllegalArgumentException("While there exists a road for the given way, the way neither "
     82            + "starts nor ends at the junction node.");
     83    }
     84   
     85    public ModelContainer getContainer() {
     86        return container;
     87    }
    8888}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Lane.java

    r25908 r26154  
    1212
    1313public class Lane {
    14         public enum Kind {
    15                 EXTRA_LEFT,
    16                 EXTRA_RIGHT,
    17                 REGULAR;
    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));
    41                 }
    42                
    43                 return result;
    44         }
    45        
    46         static List<Double> loadLengths(Relation r, String key, double lengthBound) {
    47                 final List<Double> result = new ArrayList<Double>();
    48                
    49                 if (r != null && r.get(key) != null) {
    50                         for (String s : Constants.SPLIT_PATTERN.split(r.get(key))) {
    51                                 // TODO what should the exact input be (there should probably be
    52                                 // a unit (m))
    53                                 final Double length = Double.parseDouble(s.trim());
    54                                
    55                                 if (length > lengthBound) {
    56                                         result.add(length);
    57                                 }
    58                         }
    59                 }
    60                
    61                 return result;
    62         }
    63        
    64         private static int getCount(Way w) {
    65                 final String countStr = w.get("lanes");
    66                
    67                 if (countStr != null) {
    68                         try {
    69                                 return Integer.parseInt(countStr);
    70                         } catch (NumberFormatException e) {
    71                                 throw UnexpectedDataException.Kind.INVALID_TAG_FORMAT.chuck("lanes", countStr);
    72                         }
    73                 }
    74                
    75                 throw UnexpectedDataException.Kind.MISSING_TAG.chuck("lanes");
    76         }
    77        
    78         static int getRegularCount(Way w, Node end) {
    79                 final int count = getCount(w);
    80                
    81                 if (w.hasDirectionKeys()) {
    82                         // TODO check for oneway=-1
    83                         if (w.lastNode().equals(end)) {
    84                                 return count;
    85                         } else {
    86                                 return 0;
    87                         }
    88                 } else {
    89                         if (w.lastNode().equals(end)) {
    90                                 return (count + 1) / 2; // round up in direction of end
    91                         } else {
    92                                 return count / 2; // round down in other direction
    93                         }
    94                 }
    95         }
    96        
    97         private final Road.End roadEnd;
    98         private final int index;
    99         private final Kind kind;
    100        
    101         private double length = -1;
    102        
    103         public Lane(Road.End roadEnd, int index) {
    104                 this.roadEnd = roadEnd;
    105                 this.index = index;
    106                 this.kind = Kind.REGULAR;
    107         }
    108        
    109         public Lane(Road.End roadEnd, int index, Kind kind, double length) {
    110                 assert kind == Kind.EXTRA_LEFT || kind == Kind.EXTRA_RIGHT;
    111                
    112                 this.roadEnd = roadEnd;
    113                 this.index = index;
    114                 this.kind = kind;
    115                 this.length = length;
    116                
    117                 if (length <= 0) {
    118                         throw new IllegalArgumentException("Length must be positive");
    119                 }
    120         }
    121        
    122         public Road getRoad() {
    123                 return roadEnd.getRoad();
    124         }
    125        
    126         public Kind getKind() {
    127                 return kind;
    128         }
    129        
    130         public double getLength() {
    131                 return isExtra() ? length : getRoad().getLength();
    132         }
    133        
    134         public void setLength(double length) {
    135                 if (!isExtra()) {
    136                         throw new UnsupportedOperationException("Length can only be set for extra lanes.");
    137                 } else if (length <= 0) {
    138                         throw new IllegalArgumentException("Length must positive.");
    139                 }
    140                
    141                 // TODO if needed, increase length of other lanes
    142                 getOutgoingRoadEnd().updateLengths();
    143                
    144                 this.length = length;
    145         }
    146        
    147         public boolean isExtra() {
    148                 return getKind() != Kind.REGULAR;
    149         }
    150        
    151         public int getIndex() {
    152                 return index;
    153         }
    154        
    155         public Junction getOutgoingJunction() {
    156                 return getOutgoingRoadEnd().getJunction();
    157         }
    158        
    159         public Junction getIncomingJunction() {
    160                 return getIncomingRoadEnd().getJunction();
    161         }
    162        
    163         public Road.End getOutgoingRoadEnd() {
    164                 return roadEnd;
    165         }
    166        
    167         public Road.End getIncomingRoadEnd() {
    168                 return roadEnd.getOppositeEnd();
    169         }
    170        
    171         public ModelContainer getContainer() {
    172                 return getRoad().getContainer();
    173         }
    174        
    175         public void addTurn(List<Road> via, Road.End to) {
    176                 assert equals(to.getJunction());
    177                
    178                 Relation existing = null;
    179                 for (Turn t : to.getTurns()) {
    180                         if (t.getFrom().getOutgoingRoadEnd().equals(getOutgoingRoadEnd()) && t.getVia().equals(via)) {
    181                                 if (t.getFrom().equals(this)) {
    182                                         // was already added
    183                                         return;
    184                                 }
    185                                
    186                                 existing = t.getRelation();
    187                         }
    188                 }
    189                
    190                 final Relation r;
    191                 if (existing == null) {
    192                         r = new Relation();
    193                         r.put("type", Constants.TYPE_TURNS);
    194                        
    195                         r.addMember(new RelationMember(Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay()));
    196                         if (via.isEmpty()) {
    197                                 r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, getOutgoingJunction().getNode()));
    198                         } else {
    199                                 for (Way w : Utils.flattenVia(getOutgoingJunction().getNode(), via, to.getJunction().getNode())) {
    200                                         r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, w));
    201                                 }
    202                         }
    203                         r.addMember(new RelationMember(Constants.TURN_ROLE_TO, to.getWay()));
    204                        
    205                         getOutgoingJunction().getNode().getDataSet().addPrimitive(r);
    206                 } else {
    207                         r = existing;
    208                 }
    209                
    210                 final String key = isExtra() ? Constants.TURN_KEY_EXTRA_LANES : Constants.TURN_KEY_LANES;
    211                 final List<Integer> lanes = Turn.indices(r, key);
    212                 lanes.add(getIndex());
    213                 r.put(key, Turn.join(lanes));
    214         }
    215        
    216         public Set<Turn> getTurns() {
    217                 return Turn.load(getContainer(), Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay());
    218         }
     14    public enum Kind {
     15        EXTRA_LEFT,
     16        EXTRA_RIGHT,
     17        REGULAR;
     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));
     41        }
     42       
     43        return result;
     44    }
     45   
     46    static List<Double> loadLengths(Relation r, String key, double lengthBound) {
     47        final List<Double> result = new ArrayList<Double>();
     48       
     49        if (r != null && r.get(key) != null) {
     50            for (String s : Constants.SPLIT_PATTERN.split(r.get(key))) {
     51                // TODO what should the exact input be (there should probably be
     52                // a unit (m))
     53                final Double length = Double.parseDouble(s.trim());
     54               
     55                if (length > lengthBound) {
     56                    result.add(length);
     57                }
     58            }
     59        }
     60       
     61        return result;
     62    }
     63   
     64    private static int getCount(Way w) {
     65        final String countStr = w.get("lanes");
     66       
     67        if (countStr != null) {
     68            try {
     69                return Integer.parseInt(countStr);
     70            } catch (NumberFormatException e) {
     71                throw UnexpectedDataException.Kind.INVALID_TAG_FORMAT.chuck("lanes", countStr);
     72            }
     73        }
     74       
     75        throw UnexpectedDataException.Kind.MISSING_TAG.chuck("lanes");
     76    }
     77   
     78    static int getRegularCount(Way w, Node end) {
     79        final int count = getCount(w);
     80       
     81        if (w.hasDirectionKeys()) {
     82            // TODO check for oneway=-1
     83            if (w.lastNode().equals(end)) {
     84                return count;
     85            } else {
     86                return 0;
     87            }
     88        } else {
     89            if (w.lastNode().equals(end)) {
     90                return (count + 1) / 2; // round up in direction of end
     91            } else {
     92                return count / 2; // round down in other direction
     93            }
     94        }
     95    }
     96   
     97    private final Road.End roadEnd;
     98    private final int index;
     99    private final Kind kind;
     100   
     101    private double length = -1;
     102   
     103    public Lane(Road.End roadEnd, int index) {
     104        this.roadEnd = roadEnd;
     105        this.index = index;
     106        this.kind = Kind.REGULAR;
     107    }
     108   
     109    public Lane(Road.End roadEnd, int index, Kind kind, double length) {
     110        assert kind == Kind.EXTRA_LEFT || kind == Kind.EXTRA_RIGHT;
     111       
     112        this.roadEnd = roadEnd;
     113        this.index = index;
     114        this.kind = kind;
     115        this.length = length;
     116       
     117        if (length <= 0) {
     118            throw new IllegalArgumentException("Length must be positive");
     119        }
     120    }
     121   
     122    public Road getRoad() {
     123        return roadEnd.getRoad();
     124    }
     125   
     126    public Kind getKind() {
     127        return kind;
     128    }
     129   
     130    public double getLength() {
     131        return isExtra() ? length : getRoad().getLength();
     132    }
     133   
     134    public void setLength(double length) {
     135        if (!isExtra()) {
     136            throw new UnsupportedOperationException("Length can only be set for extra lanes.");
     137        } else if (length <= 0) {
     138            throw new IllegalArgumentException("Length must positive.");
     139        }
     140       
     141        // TODO if needed, increase length of other lanes
     142        getOutgoingRoadEnd().updateLengths();
     143       
     144        this.length = length;
     145    }
     146   
     147    public boolean isExtra() {
     148        return getKind() != Kind.REGULAR;
     149    }
     150   
     151    public int getIndex() {
     152        return index;
     153    }
     154   
     155    public Junction getOutgoingJunction() {
     156        return getOutgoingRoadEnd().getJunction();
     157    }
     158   
     159    public Junction getIncomingJunction() {
     160        return getIncomingRoadEnd().getJunction();
     161    }
     162   
     163    public Road.End getOutgoingRoadEnd() {
     164        return roadEnd;
     165    }
     166   
     167    public Road.End getIncomingRoadEnd() {
     168        return roadEnd.getOppositeEnd();
     169    }
     170   
     171    public ModelContainer getContainer() {
     172        return getRoad().getContainer();
     173    }
     174   
     175    public void addTurn(List<Road> via, Road.End to) {
     176        assert equals(to.getJunction());
     177       
     178        Relation existing = null;
     179        for (Turn t : to.getTurns()) {
     180            if (t.getFrom().getOutgoingRoadEnd().equals(getOutgoingRoadEnd()) && t.getVia().equals(via)) {
     181                if (t.getFrom().equals(this)) {
     182                    // was already added
     183                    return;
     184                }
     185               
     186                existing = t.getRelation();
     187            }
     188        }
     189       
     190        final Relation r;
     191        if (existing == null) {
     192            r = new Relation();
     193            r.put("type", Constants.TYPE_TURNS);
     194           
     195            r.addMember(new RelationMember(Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay()));
     196            if (via.isEmpty()) {
     197                r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, getOutgoingJunction().getNode()));
     198            } else {
     199                for (Way w : Utils.flattenVia(getOutgoingJunction().getNode(), via, to.getJunction().getNode())) {
     200                    r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, w));
     201                }
     202            }
     203            r.addMember(new RelationMember(Constants.TURN_ROLE_TO, to.getWay()));
     204           
     205            getOutgoingJunction().getNode().getDataSet().addPrimitive(r);
     206        } else {
     207            r = existing;
     208        }
     209       
     210        final String key = isExtra() ? Constants.TURN_KEY_EXTRA_LANES : Constants.TURN_KEY_LANES;
     211        final List<Integer> lanes = Turn.indices(r, key);
     212        lanes.add(getIndex());
     213        r.put(key, Turn.join(lanes));
     214    }
     215   
     216    public Set<Turn> getTurns() {
     217        return Turn.load(getContainer(), Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay());
     218    }
    219219}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/ModelContainer.java

    r25783 r26154  
    1818
    1919public class ModelContainer {
    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;
    90         }
    91        
    92         private final Map<Node, Junction> junctions = new HashMap<Node, Junction>();
    93         private final Map<Way, Road> roads = new HashMap<Way, Road>();
    94        
    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                 }
    123         }
    124        
    125         Junction getOrCreateJunction(Node n) {
    126                 final Junction existing = junctions.get(n);
    127                
    128                 if (existing != null) {
    129                         return existing;
    130                 }
    131                
    132                 return new Junction(this, n);
    133         }
    134        
    135         public Junction getJunction(Node n) {
    136                 Junction j = junctions.get(n);
    137                
    138                 if (j == null) {
    139                         throw new IllegalArgumentException();
    140                 }
    141                
    142                 return j;
    143         }
    144        
    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);
    157                
    158                 for (Route.Segment s : newRoad.getRoute().getSegments()) {
    159                         final Road oldRoad = roads.put(s.getWay(), newRoad);
    160                        
    161                         if (oldRoad != null) {
    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);
    174         }
    175        
    176         private Road mergeRoads(Road a, Road b) {
    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);
    198         }
    199        
    200         void register(Junction j) {
    201                 if (junctions.put(j.getNode(), j) != null) {
    202                         throw new IllegalStateException();
    203                 }
    204         }
    205        
    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());
    236         }
     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;
     90    }
     91   
     92    private final Map<Node, Junction> junctions = new HashMap<Node, Junction>();
     93    private final Map<Way, Road> roads = new HashMap<Way, Road>();
     94   
     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        }
     123    }
     124   
     125    Junction getOrCreateJunction(Node n) {
     126        final Junction existing = junctions.get(n);
     127       
     128        if (existing != null) {
     129            return existing;
     130        }
     131       
     132        return new Junction(this, n);
     133    }
     134   
     135    public Junction getJunction(Node n) {
     136        Junction j = junctions.get(n);
     137       
     138        if (j == null) {
     139            throw new IllegalArgumentException();
     140        }
     141       
     142        return j;
     143    }
     144   
     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);
     157       
     158        for (Route.Segment s : newRoad.getRoute().getSegments()) {
     159            final Road oldRoad = roads.put(s.getWay(), newRoad);
     160           
     161            if (oldRoad != null) {
     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);
     174    }
     175   
     176    private Road mergeRoads(Road a, Road b) {
     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);
     198    }
     199   
     200    void register(Junction j) {
     201        if (junctions.put(j.getNode(), j) != null) {
     202            throw new IllegalStateException();
     203        }
     204    }
     205   
     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());
     236    }
    237237}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Road.java

    r25783 r26154  
    2020
    2121public class Road {
    22         public class End {
    23                 private final boolean from;
    24                 private final Junction junction;
    25                
    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;
    36                         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());
    56                 }
    57                
    58                 public Road getRoad() {
    59                         return Road.this;
    60                 }
    61                
    62                 public Way getWay() {
    63                         return isFromEnd() ? getRoute().getFirstSegment().getWay() : getRoute().getLastSegment().getWay();
    64                 }
    65                
    66                 public Junction getJunction() {
    67                         return junction;
    68                 }
    69                
    70                 public boolean isFromEnd() {
    71                         return from;
    72                 }
    73                
    74                 public boolean isToEnd() {
    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.");
    92                         }
    93                        
    94                         double length = Double.POSITIVE_INFINITY;
    95                         for (Lane l : lanes) {
    96                                 if (l.getKind() == kind) {
    97                                         length = Math.max(0, Math.min(length, l.getLength() - 1));
    98                                 }
    99                         }
    100                        
    101                         if (Double.isInfinite(length)) {
    102                                 length = Math.min(20, 3 * getLength() / 4);
    103                         }
    104                        
    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                 }
    222         }
    223        
    224         private static Pair<Relation, Relation> getLengthRelations(Way w, Node n) {
    225                 final List<Relation> left = new ArrayList<Relation>();
    226                 final List<Relation> right = new ArrayList<Relation>();
    227                
    228                 for (OsmPrimitive p : w.getReferrers()) {
    229                         if (p.getType() != OsmPrimitiveType.RELATION) {
    230                                 continue;
    231                         }
    232                        
    233                         Relation r = (Relation) p;
    234                        
    235                         if (Constants.TYPE_LENGTHS.equals(r.get("type")) && isRightDirection(r, w, n)) {
    236                                
    237                                 if (r.get(Constants.LENGTHS_KEY_LENGTHS_LEFT) != null) {
    238                                         left.add(r);
    239                                 }
    240                                
    241                                 if (r.get(Constants.LENGTHS_KEY_LENGTHS_RIGHT) != null) {
    242                                         right.add(r);
    243                                 }
    244                         }
    245                 }
    246                
    247                 if (left.size() > 1) {
    248                         throw new IllegalArgumentException("Way is in " + left.size()
    249                             + " lengths relations for given direction, both specifying left lane lengths.");
    250                 }
    251                
    252                 if (right.size() > 1) {
    253                         throw new IllegalArgumentException("Way is in " + right.size()
    254                             + " lengths relations for given direction, both specifying right lane lengths.");
    255                 }
    256                
    257                 return new Pair<Relation, Relation>( //
    258                     left.isEmpty() ? null : left.get(0), //
    259                     right.isEmpty() ? null : right.get(0) //
    260                 );
    261         }
    262        
    263         /**
    264         * @param r
    265         *          lengths relation
    266         * @param w
    267         *          the way to check for
    268         * @param n
    269         *          first or last node of w, determines the direction
    270         * @return whether the turn lane goes into the direction of n
    271         */
    272         private static boolean isRightDirection(Relation r, Way w, Node n) {
    273                 for (Segment s : Route.load(r).getSegments()) {
    274                         if (w.equals(s.getWay())) {
    275                                 return n.equals(s.getEnd());
    276                         }
    277                 }
    278                
    279                 return false;
    280         }
    281        
    282         private final ModelContainer container;
    283         private final Route route;
    284         private final End fromEnd;
    285         private final End toEnd;
    286        
    287         Road(ModelContainer container, Way w, Junction j) {
    288                 final Node n = j.getNode();
    289                 if (!w.isFirstLastNode(n)) {
    290                         throw new IllegalArgumentException("Way must start or end in given node.");
    291                 }
    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()));
    306         }
    307        
    308         public End getFromEnd() {
    309                 return fromEnd;
    310         }
    311        
    312         public End getToEnd() {
    313                 return toEnd;
    314         }
    315        
    316         public Route getRoute() {
    317                 return route;
    318         }
    319        
    320         public double getLength() {
    321                 return route.getLength();
    322         }
    323        
    324         private String toLengthString(double length) {
    325                 final DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance();
    326                 dfs.setDecimalSeparator('.');
    327                 final DecimalFormat nf = new DecimalFormat("0.0", dfs);
    328                 nf.setRoundingMode(RoundingMode.HALF_UP);
    329                 return nf.format(length);
    330         }
    331        
    332         public ModelContainer getContainer() {
    333                 return container;
    334         }
    335        
    336         public boolean isPrimary() {
    337                 return getContainer().isPrimary(this);
    338         }
     22    public class End {
     23        private final boolean from;
     24        private final Junction junction;
     25       
     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;
     36            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());
     56        }
     57       
     58        public Road getRoad() {
     59            return Road.this;
     60        }
     61       
     62        public Way getWay() {
     63            return isFromEnd() ? getRoute().getFirstSegment().getWay() : getRoute().getLastSegment().getWay();
     64        }
     65       
     66        public Junction getJunction() {
     67            return junction;
     68        }
     69       
     70        public boolean isFromEnd() {
     71            return from;
     72        }
     73       
     74        public boolean isToEnd() {
     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.");
     92            }
     93           
     94            double length = Double.POSITIVE_INFINITY;
     95            for (Lane l : lanes) {
     96                if (l.getKind() == kind) {
     97                    length = Math.max(0, Math.min(length, l.getLength() - 1));
     98                }
     99            }
     100           
     101            if (Double.isInfinite(length)) {
     102                length = Math.min(20, 3 * getLength() / 4);
     103            }
     104           
     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        }
     222    }
     223   
     224    private static Pair<Relation, Relation> getLengthRelations(Way w, Node n) {
     225        final List<Relation> left = new ArrayList<Relation>();
     226        final List<Relation> right = new ArrayList<Relation>();
     227       
     228        for (OsmPrimitive p : w.getReferrers()) {
     229            if (p.getType() != OsmPrimitiveType.RELATION) {
     230                continue;
     231            }
     232           
     233            Relation r = (Relation) p;
     234           
     235            if (Constants.TYPE_LENGTHS.equals(r.get("type")) && isRightDirection(r, w, n)) {
     236               
     237                if (r.get(Constants.LENGTHS_KEY_LENGTHS_LEFT) != null) {
     238                    left.add(r);
     239                }
     240               
     241                if (r.get(Constants.LENGTHS_KEY_LENGTHS_RIGHT) != null) {
     242                    right.add(r);
     243                }
     244            }
     245        }
     246       
     247        if (left.size() > 1) {
     248            throw new IllegalArgumentException("Way is in " + left.size()
     249                + " lengths relations for given direction, both specifying left lane lengths.");
     250        }
     251       
     252        if (right.size() > 1) {
     253            throw new IllegalArgumentException("Way is in " + right.size()
     254                + " lengths relations for given direction, both specifying right lane lengths.");
     255        }
     256       
     257        return new Pair<Relation, Relation>( //
     258            left.isEmpty() ? null : left.get(0), //
     259            right.isEmpty() ? null : right.get(0) //
     260        );
     261    }
     262   
     263    /**
     264    * @param r
     265    *          lengths relation
     266    * @param w
     267    *          the way to check for
     268    * @param n
     269    *          first or last node of w, determines the direction
     270    * @return whether the turn lane goes into the direction of n
     271    */
     272    private static boolean isRightDirection(Relation r, Way w, Node n) {
     273        for (Segment s : Route.load(r).getSegments()) {
     274            if (w.equals(s.getWay())) {
     275                return n.equals(s.getEnd());
     276            }
     277        }
     278       
     279        return false;
     280    }
     281   
     282    private final ModelContainer container;
     283    private final Route route;
     284    private final End fromEnd;
     285    private final End toEnd;
     286   
     287    Road(ModelContainer container, Way w, Junction j) {
     288        final Node n = j.getNode();
     289        if (!w.isFirstLastNode(n)) {
     290            throw new IllegalArgumentException("Way must start or end in given node.");
     291        }
     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()));
     306    }
     307   
     308    public End getFromEnd() {
     309        return fromEnd;
     310    }
     311   
     312    public End getToEnd() {
     313        return toEnd;
     314    }
     315   
     316    public Route getRoute() {
     317        return route;
     318    }
     319   
     320    public double getLength() {
     321        return route.getLength();
     322    }
     323   
     324    private String toLengthString(double length) {
     325        final DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance();
     326        dfs.setDecimalSeparator('.');
     327        final DecimalFormat nf = new DecimalFormat("0.0", dfs);
     328        nf.setRoundingMode(RoundingMode.HALF_UP);
     329        return nf.format(length);
     330    }
     331   
     332    public ModelContainer getContainer() {
     333        return container;
     334    }
     335   
     336    public boolean isPrimary() {
     337        return getContainer().isPrimary(this);
     338    }
    339339}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Route.java

    r25783 r26154  
    1010
    1111public class Route {
    12         public static final class Segment {
    13                 private final Node start;
    14                 private final Way way;
    15                 private final Node end;
    16                
    17                 private final List<Node> nodes;
    18                
    19                 Segment(Node start, Way way, Node end) {
    20                         this.start = start;
    21                         this.way = way;
    22                         this.end = end;
    23                        
    24                         final List<Node> ns = way.getNodes();
    25                         if (way.lastNode().equals(start)) {
    26                                 Collections.reverse(ns);
    27                         }
    28                        
    29                         this.nodes = Collections.unmodifiableList(ns);
    30                 }
    31                
    32                 public Node getStart() {
    33                         return start;
    34                 }
    35                
    36                 public Way getWay() {
    37                         return way;
    38                 }
    39                
    40                 public Node getEnd() {
    41                         return end;
    42                 }
    43                
    44                 public List<Node> getNodes() {
    45                         return nodes;
    46                 }
    47                
    48                 public double getLength() {
    49                         double length = 0;
    50                        
    51                         Node last = nodes.get(0);
    52                         for (Node n : nodes.subList(1, nodes.size())) {
    53                                 length += last.getCoor().greatCircleDistance(n.getCoor());
    54                                 last = n;
    55                         }
    56                        
    57                         return length;
    58                 }
    59                
    60                 @Override
    61                 public int hashCode() {
    62                         final int prime = 31;
    63                         int result = 1;
    64                         result = prime * result + ((end == null) ? 0 : end.hashCode());
    65                         result = prime * result + ((start == null) ? 0 : start.hashCode());
    66                         result = prime * result + ((way == null) ? 0 : way.hashCode());
    67                         return result;
    68                 }
    69                
    70                 @Override
    71                 public boolean equals(Object obj) {
    72                         if (this == obj)
    73                                 return true;
    74                         if (obj == null)
    75                                 return false;
    76                         if (getClass() != obj.getClass())
    77                                 return false;
    78                         Segment other = (Segment) obj;
    79                         if (end == null) {
    80                                 if (other.end != null)
    81                                         return false;
    82                         } else if (!end.equals(other.end))
    83                                 return false;
    84                         if (start == null) {
    85                                 if (other.start != null)
    86                                         return false;
    87                         } else if (!start.equals(other.start))
    88                                 return false;
    89                         if (way == null) {
    90                                 if (other.way != null)
    91                                         return false;
    92                         } else if (!way.equals(other.way))
    93                                 return false;
    94                         return true;
    95                 }
    96         }
    97        
    98         public static Route load(Relation r) {
    99                 final Node end = Utils.getMemberNode(r, Constants.LENGTHS_ROLE_END);
    100                 final List<Way> ws = Utils.getMemberWays(r, Constants.LENGTHS_ROLE_WAYS);
    101                
    102                 return create(ws, end);
    103         }
    104        
    105         public static Route load(Relation left, Relation right, Way w) {
    106                 left = left == null ? right : left;
    107                 right = right == null ? left : right;
    108                
    109                 if (left == null) {
    110                         throw new IllegalArgumentException("At least one relation must not be null.");
    111                 }
    112                
    113                 final Route leftRoute = load(left);
    114                 final Route rightRoute = load(right);
    115                
    116                 int iLeft = 0;
    117                 while (!w.equals(leftRoute.getSegments().get(iLeft++).getWay()))
    118                         ;
    119                
    120                 int iRight = 0;
    121                 while (!w.equals(rightRoute.getSegments().get(iRight++).getWay()))
    122                         ;
    123                
    124                 final int min = Math.min(iLeft, iRight);
    125                
    126                 final List<Segment> leftSegments = leftRoute.getSegments().subList(iLeft - min, iLeft);
    127                 final List<Segment> rightSegments = rightRoute.getSegments().subList(iRight - min, iRight);
    128                
    129                 if (!leftSegments.equals(rightSegments)) {
    130                         throw new IllegalArgumentException("Routes are split across different ways.");
    131                 }
    132                
    133                 return new Route(iLeft == min ? rightSegments : leftSegments);
    134         }
    135        
    136         public static Route create(List<Way> ws, Node end) {
    137                 final List<Segment> segments = new ArrayList<Segment>(ws.size());
    138                
    139                 for (Way w : ws) {
    140                         if (!w.isFirstLastNode(end)) {
    141                                 throw new IllegalArgumentException("Ways must be ordered.");
    142                         }
    143                        
    144                         final Node start = Utils.getOppositeEnd(w, end);
    145                         segments.add(0, new Segment(start, w, end));
    146                         end = start;
    147                 }
    148                
    149                 return new Route(segments);
    150         }
    151        
    152         private final List<Segment> segments;
    153        
    154         private Route(List<Segment> segments) {
    155                 this.segments = Collections.unmodifiableList(new ArrayList<Segment>(segments));
    156         }
    157        
    158         public List<Segment> getSegments() {
    159                 return segments;
    160         }
    161        
    162         public List<Node> getNodes() {
    163                 final List<Node> ns = new ArrayList<Node>();
    164                
    165                 ns.add(segments.get(0).getStart());
    166                 for (Segment s : segments) {
    167                         ns.addAll(s.getNodes().subList(1, s.getNodes().size()));
    168                 }
    169                
    170                 return Collections.unmodifiableList(ns);
    171         }
    172        
    173         public double getLengthFrom(Way w) {
    174                 double length = Double.NEGATIVE_INFINITY;
    175                
    176                 for (Segment s : getSegments()) {
    177                         length += s.getLength();
    178                        
    179                         if (w.equals(s.getWay())) {
    180                                 length = 0;
    181                         }
    182                 }
    183                
    184                 if (length < 0) {
    185                         throw new IllegalArgumentException("Way must be part of the route.");
    186                 }
    187                
    188                 return length;
    189         }
    190        
    191         public double getLength() {
    192                 double length = 0;
    193                
    194                 for (Segment s : getSegments()) {
    195                         length += s.getLength();
    196                 }
    197                
    198                 return length;
    199         }
    200        
    201         public Node getStart() {
    202                 return getFirstSegment().getStart();
    203         }
    204        
    205         public Node getEnd() {
    206                 return getLastSegment().getEnd();
    207         }
    208        
    209         public Segment getFirstSegment() {
    210                 return getSegments().get(0);
    211         }
    212        
    213         public Segment getLastSegment() {
    214                 return getSegments().get(getSegments().size() - 1);
    215         }
    216        
    217         public Route subRoute(int fromIndex, int toIndex) {
    218                 return new Route(segments.subList(fromIndex, toIndex));
    219         }
    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         }
     12    public static final class Segment {
     13        private final Node start;
     14        private final Way way;
     15        private final Node end;
     16       
     17        private final List<Node> nodes;
     18       
     19        Segment(Node start, Way way, Node end) {
     20            this.start = start;
     21            this.way = way;
     22            this.end = end;
     23           
     24            final List<Node> ns = way.getNodes();
     25            if (way.lastNode().equals(start)) {
     26                Collections.reverse(ns);
     27            }
     28           
     29            this.nodes = Collections.unmodifiableList(ns);
     30        }
     31       
     32        public Node getStart() {
     33            return start;
     34        }
     35       
     36        public Way getWay() {
     37            return way;
     38        }
     39       
     40        public Node getEnd() {
     41            return end;
     42        }
     43       
     44        public List<Node> getNodes() {
     45            return nodes;
     46        }
     47       
     48        public double getLength() {
     49            double length = 0;
     50           
     51            Node last = nodes.get(0);
     52            for (Node n : nodes.subList(1, nodes.size())) {
     53                length += last.getCoor().greatCircleDistance(n.getCoor());
     54                last = n;
     55            }
     56           
     57            return length;
     58        }
     59       
     60        @Override
     61        public int hashCode() {
     62            final int prime = 31;
     63            int result = 1;
     64            result = prime * result + ((end == null) ? 0 : end.hashCode());
     65            result = prime * result + ((start == null) ? 0 : start.hashCode());
     66            result = prime * result + ((way == null) ? 0 : way.hashCode());
     67            return result;
     68        }
     69       
     70        @Override
     71        public boolean equals(Object obj) {
     72            if (this == obj)
     73                return true;
     74            if (obj == null)
     75                return false;
     76            if (getClass() != obj.getClass())
     77                return false;
     78            Segment other = (Segment) obj;
     79            if (end == null) {
     80                if (other.end != null)
     81                    return false;
     82            } else if (!end.equals(other.end))
     83                return false;
     84            if (start == null) {
     85                if (other.start != null)
     86                    return false;
     87            } else if (!start.equals(other.start))
     88                return false;
     89            if (way == null) {
     90                if (other.way != null)
     91                    return false;
     92            } else if (!way.equals(other.way))
     93                return false;
     94            return true;
     95        }
     96    }
     97   
     98    public static Route load(Relation r) {
     99        final Node end = Utils.getMemberNode(r, Constants.LENGTHS_ROLE_END);
     100        final List<Way> ws = Utils.getMemberWays(r, Constants.LENGTHS_ROLE_WAYS);
     101       
     102        return create(ws, end);
     103    }
     104   
     105    public static Route load(Relation left, Relation right, Way w) {
     106        left = left == null ? right : left;
     107        right = right == null ? left : right;
     108       
     109        if (left == null) {
     110            throw new IllegalArgumentException("At least one relation must not be null.");
     111        }
     112       
     113        final Route leftRoute = load(left);
     114        final Route rightRoute = load(right);
     115       
     116        int iLeft = 0;
     117        while (!w.equals(leftRoute.getSegments().get(iLeft++).getWay()))
     118            ;
     119       
     120        int iRight = 0;
     121        while (!w.equals(rightRoute.getSegments().get(iRight++).getWay()))
     122            ;
     123       
     124        final int min = Math.min(iLeft, iRight);
     125       
     126        final List<Segment> leftSegments = leftRoute.getSegments().subList(iLeft - min, iLeft);
     127        final List<Segment> rightSegments = rightRoute.getSegments().subList(iRight - min, iRight);
     128       
     129        if (!leftSegments.equals(rightSegments)) {
     130            throw new IllegalArgumentException("Routes are split across different ways.");
     131        }
     132       
     133        return new Route(iLeft == min ? rightSegments : leftSegments);
     134    }
     135   
     136    public static Route create(List<Way> ws, Node end) {
     137        final List<Segment> segments = new ArrayList<Segment>(ws.size());
     138       
     139        for (Way w : ws) {
     140            if (!w.isFirstLastNode(end)) {
     141                throw new IllegalArgumentException("Ways must be ordered.");
     142            }
     143           
     144            final Node start = Utils.getOppositeEnd(w, end);
     145            segments.add(0, new Segment(start, w, end));
     146            end = start;
     147        }
     148       
     149        return new Route(segments);
     150    }
     151   
     152    private final List<Segment> segments;
     153   
     154    private Route(List<Segment> segments) {
     155        this.segments = Collections.unmodifiableList(new ArrayList<Segment>(segments));
     156    }
     157   
     158    public List<Segment> getSegments() {
     159        return segments;
     160    }
     161   
     162    public List<Node> getNodes() {
     163        final List<Node> ns = new ArrayList<Node>();
     164       
     165        ns.add(segments.get(0).getStart());
     166        for (Segment s : segments) {
     167            ns.addAll(s.getNodes().subList(1, s.getNodes().size()));
     168        }
     169       
     170        return Collections.unmodifiableList(ns);
     171    }
     172   
     173    public double getLengthFrom(Way w) {
     174        double length = Double.NEGATIVE_INFINITY;
     175       
     176        for (Segment s : getSegments()) {
     177            length += s.getLength();
     178           
     179            if (w.equals(s.getWay())) {
     180                length = 0;
     181            }
     182        }
     183       
     184        if (length < 0) {
     185            throw new IllegalArgumentException("Way must be part of the route.");
     186        }
     187       
     188        return length;
     189    }
     190   
     191    public double getLength() {
     192        double length = 0;
     193       
     194        for (Segment s : getSegments()) {
     195            length += s.getLength();
     196        }
     197       
     198        return length;
     199    }
     200   
     201    public Node getStart() {
     202        return getFirstSegment().getStart();
     203    }
     204   
     205    public Node getEnd() {
     206        return getLastSegment().getEnd();
     207    }
     208   
     209    public Segment getFirstSegment() {
     210        return getSegments().get(0);
     211    }
     212   
     213    public Segment getLastSegment() {
     214        return getSegments().get(getSegments().size() - 1);
     215    }
     216   
     217    public Route subRoute(int fromIndex, int toIndex) {
     218        return new Route(segments.subList(fromIndex, toIndex));
     219    }
     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    }
    230230}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Turn.java

    r25783 r26154  
    1717
    1818public final class Turn {
    19         static Set<Turn> load(ModelContainer c, String role, OsmPrimitive primitive) {
    20                 final Set<Turn> result = new HashSet<Turn>();
    21                
    22                 for (Relation r : OsmPrimitive.getFilteredList(primitive.getReferrers(), Relation.class)) {
    23                         if (!r.get("type").equals(Constants.TYPE_TURNS)) {
    24                                 continue;
    25                         }
    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                         }
    32                 }
    33                
    34                 return result;
    35         }
    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);
    44                                 }
    45                         }
    46                 }
    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.");
    70                         }
    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                                 }
    83                         }
    84                 }
    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                 }
    95                 return result;
    96         }
    97        
    98         static List<Integer> indices(Relation r, String key) {
    99                 final String joined = r.get(key);
    100                
    101                 if (joined == null) {
    102                         return new ArrayList<Integer>(1);
    103                 }
    104                
    105                 final List<Integer> result = new ArrayList<Integer>();
    106                 for (String lane : Constants.SPLIT_PATTERN.split(joined)) {
    107                         result.add(Integer.parseInt(lane));
    108                 }
    109                
    110                 return result;
    111         }
    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        
    133         static String join(List<Integer> list) {
    134                 if (list.isEmpty()) {
    135                         return null;
    136                 }
    137                
    138                 final StringBuilder builder = new StringBuilder(list.size() * (2 + Constants.SEPARATOR.length()));
    139                
    140                 for (int e : list) {
    141                         builder.append(e).append(Constants.SEPARATOR);
    142                 }
    143                
    144                 builder.setLength(builder.length() - Constants.SEPARATOR.length());
    145                 return builder.toString();
    146         }
    147        
    148         private final Relation relation;
    149        
    150         private final Lane from;
    151         private final List<Road> via;
    152         private final Road.End to;
    153        
    154         public Turn(Relation relation, Lane from, List<Road> via, Road.End to) {
    155                 this.relation = relation;
    156                 this.from = from;
    157                 this.via = via;
    158                 this.to = to;
    159         }
    160        
    161         public Lane getFrom() {
    162                 return from;
    163         }
    164        
    165         public List<Road> getVia() {
    166                 return via;
    167         }
    168        
    169         public Road.End getTo() {
    170                 return to;
    171         }
    172        
    173         Relation getRelation() {
    174                 return relation;
    175         }
    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())) {
    182                         relation.getDataSet().removePrimitive(relation.getPrimitiveId());
    183                 } else if (from.isExtra()) {
    184                         extraLanes.remove(Integer.valueOf(from.getIndex()));
    185                 } else {
    186                         lanes.remove(Integer.valueOf(from.getIndex()));
    187                 }
    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));
    191         }
     19    static Set<Turn> load(ModelContainer c, String role, OsmPrimitive primitive) {
     20        final Set<Turn> result = new HashSet<Turn>();
     21       
     22        for (Relation r : OsmPrimitive.getFilteredList(primitive.getReferrers(), Relation.class)) {
     23            if (!r.get("type").equals(Constants.TYPE_TURNS)) {
     24                continue;
     25            }
     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            }
     32        }
     33       
     34        return result;
     35    }
     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);
     44                }
     45            }
     46        }
     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.");
     70            }
     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                }
     83            }
     84        }
     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        }
     95        return result;
     96    }
     97   
     98    static List<Integer> indices(Relation r, String key) {
     99        final String joined = r.get(key);
     100       
     101        if (joined == null) {
     102            return new ArrayList<Integer>(1);
     103        }
     104       
     105        final List<Integer> result = new ArrayList<Integer>();
     106        for (String lane : Constants.SPLIT_PATTERN.split(joined)) {
     107            result.add(Integer.parseInt(lane));
     108        }
     109       
     110        return result;
     111    }
     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   
     133    static String join(List<Integer> list) {
     134        if (list.isEmpty()) {
     135            return null;
     136        }
     137       
     138        final StringBuilder builder = new StringBuilder(list.size() * (2 + Constants.SEPARATOR.length()));
     139       
     140        for (int e : list) {
     141            builder.append(e).append(Constants.SEPARATOR);
     142        }
     143       
     144        builder.setLength(builder.length() - Constants.SEPARATOR.length());
     145        return builder.toString();
     146    }
     147   
     148    private final Relation relation;
     149   
     150    private final Lane from;
     151    private final List<Road> via;
     152    private final Road.End to;
     153   
     154    public Turn(Relation relation, Lane from, List<Road> via, Road.End to) {
     155        this.relation = relation;
     156        this.from = from;
     157        this.via = via;
     158        this.to = to;
     159    }
     160   
     161    public Lane getFrom() {
     162        return from;
     163    }
     164   
     165    public List<Road> getVia() {
     166        return via;
     167    }
     168   
     169    public Road.End getTo() {
     170        return to;
     171    }
     172   
     173    Relation getRelation() {
     174        return relation;
     175    }
     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())) {
     182            relation.getDataSet().removePrimitive(relation.getPrimitiveId());
     183        } else if (from.isExtra()) {
     184            extraLanes.remove(Integer.valueOf(from.getIndex()));
     185        } else {
     186            lanes.remove(Integer.valueOf(from.getIndex()));
     187        }
     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));
     191    }
    192192}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/UnexpectedDataException.java

    r25908 r26154  
    66
    77public final class UnexpectedDataException extends RuntimeException {
    8         private static final long serialVersionUID = 7430280313889494242L;
    9        
    10         public enum Kind {
    11                 NO_MEMBER("No member with role \"{0}\".", 1),
    12                 MULTIPLE_MEMBERS("More than one member with role \"{0}\".", 1),
    13                 WRONG_MEMBER_TYPE("A member with role \"{0}\" is a {1} and not a {2} as expected.", 3),
    14                 INVALID_TAG_FORMAT("The tag \"{0}\" has an invalid format: {1}", 2),
    15                 MISSING_TAG("The tag \"{0}\" is missing.", 1);
    16                
    17                 private final String message;
    18                 private final int params;
    19                
    20                 private Kind(String message, int params) {
    21                         this.message = message;
    22                         this.params = params;
    23                 }
    24                
    25                 public UnexpectedDataException chuck(Object... args) {
    26                         throw new UnexpectedDataException(this, format(args));
    27                 }
    28                
    29                 public String format(Object... args) {
    30                         if (args.length != params) {
    31                                 throw new IllegalArgumentException("Wrong argument count for " + this + ": " + Arrays.toString(args));
    32                         }
    33                        
    34                         return tr(message, args);
    35                 }
    36         }
    37        
    38         private final Kind kind;
    39        
    40         public UnexpectedDataException(Kind kind, String message) {
    41                 super(message);
    42                
    43                 this.kind = kind;
    44         }
    45        
    46         public Kind getKind() {
    47                 return kind;
    48         }
     8    private static final long serialVersionUID = 7430280313889494242L;
     9   
     10    public enum Kind {
     11        NO_MEMBER("No member with role \"{0}\".", 1),
     12        MULTIPLE_MEMBERS("More than one member with role \"{0}\".", 1),
     13        WRONG_MEMBER_TYPE("A member with role \"{0}\" is a {1} and not a {2} as expected.", 3),
     14        INVALID_TAG_FORMAT("The tag \"{0}\" has an invalid format: {1}", 2),
     15        MISSING_TAG("The tag \"{0}\" is missing.", 1);
     16       
     17        private final String message;
     18        private final int params;
     19       
     20        private Kind(String message, int params) {
     21            this.message = message;
     22            this.params = params;
     23        }
     24       
     25        public UnexpectedDataException chuck(Object... args) {
     26            throw new UnexpectedDataException(this, format(args));
     27        }
     28       
     29        public String format(Object... args) {
     30            if (args.length != params) {
     31                throw new IllegalArgumentException("Wrong argument count for " + this + ": " + Arrays.toString(args));
     32            }
     33           
     34            return tr(message, args);
     35        }
     36    }
     37   
     38    private final Kind kind;
     39   
     40    public UnexpectedDataException(Kind kind, String message) {
     41        super(message);
     42       
     43        this.kind = kind;
     44    }
     45   
     46    public Kind getKind() {
     47        return kind;
     48    }
    4949}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Utils.java

    r25908 r26154  
    2020
    2121public class Utils {
    22         private static final Set<String> ROAD_HIGHWAY_VALUES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
    23             "motorway", "motorway_link", "trunk", "trunk_link", "primary", "primary_link", "secondary", "secondary_link",
    24             "tertiary", "residential", "unclassified", "road", "living_street", "service", "track", "pedestrian", "raceway",
    25             "services")));
    26        
    27         public static boolean isRoad(Way w) {
    28                 return ROAD_HIGHWAY_VALUES.contains(w.get("highway"));
    29         }
    30        
    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        
    43         public static Node getMemberNode(Relation r, String role) {
    44                 return getMember(r, role, OsmPrimitiveType.NODE).getNode();
    45         }
    46        
    47         public static Way getMemberWay(Relation r, String role) {
    48                 return getMember(r, role, OsmPrimitiveType.WAY).getWay();
    49         }
    50        
    51         public static RelationMember getMember(Relation r, String role, OsmPrimitiveType type) {
    52                 final List<RelationMember> candidates = getMembers(r, role, type);
    53                 if (candidates.isEmpty()) {
    54                         throw UnexpectedDataException.Kind.NO_MEMBER.chuck(role);
    55                 } else if (candidates.size() > 1) {
    56                         throw UnexpectedDataException.Kind.MULTIPLE_MEMBERS.chuck(role);
    57                 }
    58                 return candidates.get(0);
    59         }
    60        
    61         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 UnexpectedDataException.Kind.WRONG_MEMBER_TYPE.chuck(role, m.getType(), type);
    66                         }
    67                 }
    68                 return result;
    69         }
    70        
    71         public static List<RelationMember> getMembers(Relation r, String role) {
    72                 final List<RelationMember> result = new ArrayList<RelationMember>();
    73                 for (RelationMember m : r.getMembers()) {
    74                         if (m.getRole().equals(role)) {
    75                                 result.add(m);
    76                         }
    77                 }
    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);
    83         }
    84        
    85         public static List<Way> getMemberWays(Relation r, String role) {
    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                 }
    118         }
    119        
    120         public static Node getOppositeEnd(Way w, Node n) {
    121                 final boolean first = n.equals(w.firstNode());
    122                 final boolean last = n.equals(w.lastNode());
    123                
    124                 if (first && last) {
    125                         throw new IllegalArgumentException("Way starts as well as ends at the given node.");
    126                 } else if (first) {
    127                         return w.lastNode();
    128                 } else if (last) {
    129                         return w.firstNode();
    130                 } else {
    131                         throw new IllegalArgumentException("Way neither starts nor ends at given node.");
    132                 }
    133         }
    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         }
     22    private static final Set<String> ROAD_HIGHWAY_VALUES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
     23        "motorway", "motorway_link", "trunk", "trunk_link", "primary", "primary_link", "secondary", "secondary_link",
     24        "tertiary", "residential", "unclassified", "road", "living_street", "service", "track", "pedestrian", "raceway",
     25        "services")));
     26   
     27    public static boolean isRoad(Way w) {
     28        return ROAD_HIGHWAY_VALUES.contains(w.get("highway"));
     29    }
     30   
     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   
     43    public static Node getMemberNode(Relation r, String role) {
     44        return getMember(r, role, OsmPrimitiveType.NODE).getNode();
     45    }
     46   
     47    public static Way getMemberWay(Relation r, String role) {
     48        return getMember(r, role, OsmPrimitiveType.WAY).getWay();
     49    }
     50   
     51    public static RelationMember getMember(Relation r, String role, OsmPrimitiveType type) {
     52        final List<RelationMember> candidates = getMembers(r, role, type);
     53        if (candidates.isEmpty()) {
     54            throw UnexpectedDataException.Kind.NO_MEMBER.chuck(role);
     55        } else if (candidates.size() > 1) {
     56            throw UnexpectedDataException.Kind.MULTIPLE_MEMBERS.chuck(role);
     57        }
     58        return candidates.get(0);
     59    }
     60   
     61    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 UnexpectedDataException.Kind.WRONG_MEMBER_TYPE.chuck(role, m.getType(), type);
     66            }
     67        }
     68        return result;
     69    }
     70   
     71    public static List<RelationMember> getMembers(Relation r, String role) {
     72        final List<RelationMember> result = new ArrayList<RelationMember>();
     73        for (RelationMember m : r.getMembers()) {
     74            if (m.getRole().equals(role)) {
     75                result.add(m);
     76            }
     77        }
     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);
     83    }
     84   
     85    public static List<Way> getMemberWays(Relation r, String role) {
     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        }
     118    }
     119   
     120    public static Node getOppositeEnd(Way w, Node n) {
     121        final boolean first = n.equals(w.firstNode());
     122        final boolean last = n.equals(w.lastNode());
     123       
     124        if (first && last) {
     125            throw new IllegalArgumentException("Way starts as well as ends at the given node.");
     126        } else if (first) {
     127            return w.lastNode();
     128        } else if (last) {
     129            return w.firstNode();
     130        } else {
     131            throw new IllegalArgumentException("Way neither starts nor ends at given node.");
     132        }
     133    }
     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    }
    225225}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Validator.java

    r25908 r26154  
    2323
    2424public class Validator {
    25         private static final class IncomingLanes {
    26                 private static final class Key {
    27                         final Node junction;
    28                         final Way from;
    29                        
    30                         public Key(Node junction, Way from) {
    31                                 this.junction = junction;
    32                                 this.from = from;
    33                         }
    34                        
    35                         @Override
    36                         public int hashCode() {
    37                                 final int prime = 31;
    38                                 int result = 1;
    39                                 result = prime * result + ((from == null) ? 0 : from.hashCode());
    40                                 result = prime * result + ((junction == null) ? 0 : junction.hashCode());
    41                                 return result;
    42                         }
    43                        
    44                         @Override
    45                         public boolean equals(Object obj) {
    46                                 if (this == obj)
    47                                         return true;
    48                                 if (obj == null)
    49                                         return false;
    50                                 if (getClass() != obj.getClass())
    51                                         return false;
    52                                 Key other = (Key) obj;
    53                                 if (from == null) {
    54                                         if (other.from != null)
    55                                                 return false;
    56                                 } else if (!from.equals(other.from))
    57                                         return false;
    58                                 if (junction == null) {
    59                                         if (other.junction != null)
    60                                                 return false;
    61                                 } else if (!junction.equals(other.junction))
    62                                         return false;
    63                                 return true;
    64                         }
    65                 }
    66                
    67                 final Key key;
    68                 private final int extraLeft;
    69                 private final int regular;
    70                 private final int extraRight;
    71                 private final BitSet bitset;
    72                
    73                 public IncomingLanes(Key key, int extraLeft, int regular, int extraRight) {
    74                         this.key = key;
    75                         this.extraLeft = extraLeft;
    76                         this.regular = regular;
    77                         this.extraRight = extraRight;
    78                         this.bitset = new BitSet(extraLeft + regular + extraRight);
    79                 }
    80                
    81                 public boolean existsRegular(int l) {
    82                         if (l > 0 && l <= regular) {
    83                                 bitset.set(extraLeft + l - 1);
    84                                 return true;
    85                         }
    86                        
    87                         return false;
    88                 }
    89                
    90                 public boolean existsExtra(int l) {
    91                         if (l < 0 && Math.abs(l) <= extraLeft) {
    92                                 bitset.set(Math.abs(l) - 1);
    93                                 return true;
    94                         } else if (l > 0 && l <= extraRight) {
    95                                 bitset.set(extraLeft + regular + l - 1);
    96                                 return true;
    97                         }
    98                         return false;
    99                 }
    100                
    101                 public int unreferenced() {
    102                         return extraLeft + regular + extraRight - bitset.cardinality();
    103                 }
    104         }
    105        
    106         public List<Issue> validate(DataSet dataSet) {
    107                 final List<Relation> lenghts = new ArrayList<Relation>();
    108                 final List<Relation> turns = new ArrayList<Relation>();
    109                
    110                 for (Relation r : OsmPrimitive.getFilteredList(dataSet.allPrimitives(), Relation.class)) {
    111                         final String type = r.get("type");
    112                        
    113                         if (Constants.TYPE_LENGTHS.equals(type)) {
    114                                 lenghts.add(r);
    115                         } else if (Constants.TYPE_TURNS.equals(type)) {
    116                                 turns.add(r);
    117                         }
    118                 }
    119                
    120                 final List<Issue> issues = new ArrayList<Issue>();
    121                
    122                 final Map<IncomingLanes.Key, IncomingLanes> incomingLanes = new HashMap<IncomingLanes.Key, IncomingLanes>();
    123                 issues.addAll(validateLengths(lenghts, incomingLanes));
    124                 issues.addAll(validateTurns(turns, incomingLanes));
    125                
    126                 for (IncomingLanes lanes : incomingLanes.values()) {
    127                         if (lanes.unreferenced() > 0) {
    128                                 issues.add(Issue.newWarning(Arrays.asList(lanes.key.junction, lanes.key.from),
    129                                     tr("{0} lanes are not referenced in any turn-relation.", lanes.unreferenced())));
    130                         }
    131                 }
    132                
    133                 return issues;
    134         }
    135        
    136         private List<Issue> validateLengths(List<Relation> lenghts, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
    137                 final List<Issue> issues = new ArrayList<Issue>();
    138                
    139                 for (Relation r : lenghts) {
    140                         issues.addAll(validateLengths(r, incomingLanes));
    141                 }
    142                
    143                 return issues;
    144         }
    145        
    146         private List<Issue> validateLengths(Relation r, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
    147                 final List<Issue> issues = new ArrayList<Issue>();
    148                
    149                 try {
    150                         final Node end = Utils.getMemberNode(r, Constants.LENGTHS_ROLE_END);
    151                         final Route route = validateLengthsWays(r, end, issues);
    152                        
    153                         if (route == null) {
    154                                 return issues;
    155                         }
    156                        
    157                         final List<Double> left = Lane.loadLengths(r, Constants.LENGTHS_KEY_LENGTHS_LEFT, 0);
    158                         final List<Double> right = Lane.loadLengths(r, Constants.LENGTHS_KEY_LENGTHS_RIGHT, 0);
    159                        
    160                         int tooLong = 0;
    161                         for (Double l : left) {
    162                                 if (l > route.getLength()) {
    163                                         ++tooLong;
    164                                 }
    165                         }
    166                         for (Double l : right) {
    167                                 if (l > route.getLength()) {
    168                                         ++tooLong;
    169                                 }
    170                         }
    171                        
    172                         if (tooLong > 0) {
    173                                 issues.add(Issue.newError(r, end, "The lengths-relation specifies " + tooLong
    174                                     + " extra-lanes which are longer than its ways."));
    175                         }
    176                        
    177                         putIncomingLanes(route, left, right, incomingLanes);
    178                        
    179                         return issues;
    180                        
    181                 } catch (UnexpectedDataException e) {
    182                         issues.add(Issue.newError(r, e.getMessage()));
    183                         return issues;
    184                 }
    185         }
    186        
    187         private void putIncomingLanes(Route route, List<Double> left, List<Double> right,
    188             Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
    189                 final Node end = route.getLastSegment().getEnd();
    190                 final Way way = route.getLastSegment().getWay();
    191                
    192                 final IncomingLanes.Key key = new IncomingLanes.Key(end, way);
    193                 final IncomingLanes lanes = new IncomingLanes(key, left.size(), Lane.getRegularCount(way, end), right.size());
    194                 final IncomingLanes old = incomingLanes.put(key, lanes);
    195                
    196                 if (old != null) {
    197                         incomingLanes.put(
    198                             key,
    199                             new IncomingLanes(key, Math.max(lanes.extraLeft, old.extraLeft), Math.max(lanes.regular, old.regular), Math
    200                                 .max(lanes.extraRight, old.extraRight)));
    201                 }
    202                
    203                 // TODO this tends to produce a bunch of useless errors
    204                 // turn lanes really should not span from one junction past another, remove??
    205                 //              final double length = route.getLastSegment().getLength();
    206                 //              final List<Double> newLeft = reduceLengths(left, length);
    207                 //              final List<Double> newRight = new ArrayList<Double>(right.size());
    208                 //             
    209                 //              if (route.getSegments().size() > 1) {
    210                 //                      final Route subroute = route.subRoute(0, route.getSegments().size() - 1);
    211                 //                      putIncomingLanes(subroute, newLeft, newRight, incomingLanes);
    212                 //              }
    213         }
    214        
    215         private List<Double> reduceLengths(List<Double> lengths, double length) {
    216                 final List<Double> newLengths = new ArrayList<Double>(lengths.size());
    217                
    218                 for (double l : lengths) {
    219                         if (l > length) {
    220                                 newLengths.add(l - length);
    221                         }
    222                 }
    223                
    224                 return newLengths;
    225         }
    226        
    227         private Route validateLengthsWays(Relation r, Node end, List<Issue> issues) {
    228                 final List<Way> ways = Utils.getMemberWays(r, Constants.LENGTHS_ROLE_WAYS);
    229                
    230                 if (ways.isEmpty()) {
    231                         issues.add(Issue.newError(r, "A lengths-relation requires at least one member-way with role \""
    232                             + Constants.LENGTHS_ROLE_WAYS + "\"."));
    233                         return null;
    234                 }
    235                
    236                 Node current = end;
    237                 for (Way w : ways) {
    238                         if (!w.isFirstLastNode(current)) {
    239                                 return orderWays(r, ways, current, issues, "ways", "lengths");
    240                         }
    241                        
    242                         current = Utils.getOppositeEnd(w, current);
    243                 }
    244                
    245                 return Route.create(ways, end);
    246         }
    247        
    248         private Route orderWays(final Relation r, List<Way> ways, Node end, List<Issue> issues, String role, String type) {
    249                 final List<Way> unordered = new ArrayList<Way>(ways);
    250                 final List<Way> ordered = new ArrayList<Way>(ways.size());
    251                 final Set<Node> ends = new HashSet<Node>(); // to find cycles
    252                
    253                 Node current = end;
    254                 findNext: while (!unordered.isEmpty()) {
    255                         if (!ends.add(current)) {
    256                                 issues.add(Issue.newError(r, ways, "The " + role + " of the " + type
    257                                     + "-relation are unordered (and contain cycles)."));
    258                                 return null;
    259                         }
    260                        
    261                         Iterator<Way> it = unordered.iterator();
    262                         while (it.hasNext()) {
    263                                 final Way w = it.next();
    264                                
    265                                 if (w.isFirstLastNode(current)) {
    266                                         it.remove();
    267                                         ordered.add(w);
    268                                         current = Utils.getOppositeEnd(w, current);
    269                                         continue findNext;
    270                                 }
    271                         }
    272                        
    273                         issues.add(Issue.newError(r, ways, "The " + role + " of the " + type + "-relation are disconnected."));
    274                         return null;
    275                 }
    276                
    277                 final QuickFix quickFix = new QuickFix(tr("Put the ways in order.")) {
    278                         @Override
    279                         public boolean perform() {
    280                                 for (int i = r.getMembersCount() - 1; i >= 0; --i) {
    281                                         final RelationMember m = r.getMember(i);
    282                                        
    283                                         if (m.isWay() && Constants.LENGTHS_ROLE_WAYS.equals(m.getRole())) {
    284                                                 r.removeMember(i);
    285                                         }
    286                                 }
    287                                
    288                                 for (Way w : ordered) {
    289                                         r.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, w));
    290                                 }
    291                                
    292                                 return true;
    293                         }
    294                 };
    295                
    296                 issues.add(Issue.newError(r, ways, "The ways of the lengths-relation are unordered.", quickFix));
    297                
    298                 return Route.create(ordered, end);
    299         }
    300        
    301         private List<Issue> validateTurns(List<Relation> turns, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
    302                 final List<Issue> issues = new ArrayList<Issue>();
    303                
    304                 for (Relation r : turns) {
    305                         issues.addAll(validateTurns(r, incomingLanes));
    306                 }
    307                
    308                 return issues;
    309         }
    310        
    311         private List<Issue> validateTurns(Relation r, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
    312                 final List<Issue> issues = new ArrayList<Issue>();
    313                
    314                 try {
    315                         final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
    316                         final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
    317                        
    318                         if (from.firstNode().equals(from.lastNode())) {
    319                                 issues.add(Issue.newError(r, from, "The from-way both starts as well as ends at the via-node."));
    320                         }
    321                         if (to.firstNode().equals(to.lastNode())) {
    322                                 issues.add(Issue.newError(r, to, "The to-way both starts as well as ends at the via-node."));
    323                         }
    324                         if (!issues.isEmpty()) {
    325                                 return issues;
    326                         }
    327                        
    328                         final Node fromJunctionNode;
    329                         final List<RelationMember> viaMembers = Utils.getMembers(r, Constants.TURN_ROLE_VIA);
    330                         if (viaMembers.isEmpty()) {
    331                                 throw UnexpectedDataException.Kind.NO_MEMBER.chuck(Constants.TURN_ROLE_VIA);
    332                         } else if (viaMembers.get(0).isWay()) {
    333                                 final List<Way> vias = Utils.getMemberWays(r, Constants.TURN_ROLE_VIA);
    334                                
    335                                 fromJunctionNode = Utils.lineUp(from, vias.get(0));
    336                                 Node current = fromJunctionNode;
    337                                 for (Way via : vias) {
    338                                         if (!via.isFirstLastNode(current)) {
    339                                                 orderWays(r, vias, current, issues, "via-ways", "turns");
    340                                                 break;
    341                                         }
    342                                        
    343                                         current = Utils.getOppositeEnd(via, current);
    344                                 }
    345                         } else {
    346                                 final Node via = Utils.getMemberNode(r, Constants.TURN_ROLE_VIA);
    347                                
    348                                 if (!from.isFirstLastNode(via)) {
    349                                         issues.add(Issue.newError(r, from, "The from-way does not start or end at the via-node."));
    350                                 }
    351                                 if (!to.isFirstLastNode(via)) {
    352                                         issues.add(Issue.newError(r, to, "The to-way does not start or end at the via-node."));
    353                                 }
    354                                
    355                                 fromJunctionNode = via;
    356                         }
    357                        
    358                         if (!issues.isEmpty()) {
    359                                 return issues;
    360                         }
    361                         final IncomingLanes lanes = get(incomingLanes, fromJunctionNode, from);
    362                        
    363                         for (int l : splitInts(r, Constants.TURN_KEY_LANES, issues)) {
    364                                 if (!lanes.existsRegular(l)) {
    365                                         issues.add(Issue.newError(r, tr("Relation references non-existent (regular) lane {0}", l)));
    366                                 }
    367                         }
    368                        
    369                         for (int l : splitInts(r, Constants.TURN_KEY_EXTRA_LANES, issues)) {
    370                                 if (!lanes.existsExtra(l)) {
    371                                         issues.add(Issue.newError(r, tr("Relation references non-existent extra lane {0}", l)));
    372                                 }
    373                         }
    374                        
    375                         return issues;
    376                 } catch (UnexpectedDataException e) {
    377                         issues.add(Issue.newError(r, e.getMessage()));
    378                         return issues;
    379                 }
    380         }
    381        
    382         private List<Integer> splitInts(Relation r, String key, List<Issue> issues) {
    383                 final String ints = r.get(key);
    384                
    385                 if (ints == null) {
    386                         return Collections.emptyList();
    387                 }
    388                
    389                 final List<Integer> result = new ArrayList<Integer>();
    390                
    391                 for (String s : Constants.SPLIT_PATTERN.split(ints)) {
    392                         try {
    393                                 int i = Integer.parseInt(s.trim());
    394                                 result.add(Integer.valueOf(i));
    395                         } catch (NumberFormatException e) {
    396                                 issues.add(Issue.newError(r, tr("Integer list \"{0}\" contains unexpected values.", key)));
    397                         }
    398                 }
    399                
    400                 return result;
    401         }
    402        
    403         private IncomingLanes get(Map<IncomingLanes.Key, IncomingLanes> incomingLanes, Node via, Way from) {
    404                 final IncomingLanes.Key key = new IncomingLanes.Key(via, from);
    405                 final IncomingLanes lanes = incomingLanes.get(key);
    406                
    407                 if (lanes == null) {
    408                         final IncomingLanes newLanes = new IncomingLanes(key, 0, Lane.getRegularCount(from, via), 0);
    409                         incomingLanes.put(key, newLanes);
    410                         return newLanes;
    411                 } else {
    412                         return lanes;
    413                 }
    414         }
     25    private static final class IncomingLanes {
     26        private static final class Key {
     27            final Node junction;
     28            final Way from;
     29           
     30            public Key(Node junction, Way from) {
     31                this.junction = junction;
     32                this.from = from;
     33            }
     34           
     35            @Override
     36            public int hashCode() {
     37                final int prime = 31;
     38                int result = 1;
     39                result = prime * result + ((from == null) ? 0 : from.hashCode());
     40                result = prime * result + ((junction == null) ? 0 : junction.hashCode());
     41                return result;
     42            }
     43           
     44            @Override
     45            public boolean equals(Object obj) {
     46                if (this == obj)
     47                    return true;
     48                if (obj == null)
     49                    return false;
     50                if (getClass() != obj.getClass())
     51                    return false;
     52                Key other = (Key) obj;
     53                if (from == null) {
     54                    if (other.from != null)
     55                        return false;
     56                } else if (!from.equals(other.from))
     57                    return false;
     58                if (junction == null) {
     59                    if (other.junction != null)
     60                        return false;
     61                } else if (!junction.equals(other.junction))
     62                    return false;
     63                return true;
     64            }
     65        }
     66       
     67        final Key key;
     68        private final int extraLeft;
     69        private final int regular;
     70        private final int extraRight;
     71        private final BitSet bitset;
     72       
     73        public IncomingLanes(Key key, int extraLeft, int regular, int extraRight) {
     74            this.key = key;
     75            this.extraLeft = extraLeft;
     76            this.regular = regular;
     77            this.extraRight = extraRight;
     78            this.bitset = new BitSet(extraLeft + regular + extraRight);
     79        }
     80       
     81        public boolean existsRegular(int l) {
     82            if (l > 0 && l <= regular) {
     83                bitset.set(extraLeft + l - 1);
     84                return true;
     85            }
     86           
     87            return false;
     88        }
     89       
     90        public boolean existsExtra(int l) {
     91            if (l < 0 && Math.abs(l) <= extraLeft) {
     92                bitset.set(Math.abs(l) - 1);
     93                return true;
     94            } else if (l > 0 && l <= extraRight) {
     95                bitset.set(extraLeft + regular + l - 1);
     96                return true;
     97            }
     98            return false;
     99        }
     100       
     101        public int unreferenced() {
     102            return extraLeft + regular + extraRight - bitset.cardinality();
     103        }
     104    }
     105   
     106    public List<Issue> validate(DataSet dataSet) {
     107        final List<Relation> lenghts = new ArrayList<Relation>();
     108        final List<Relation> turns = new ArrayList<Relation>();
     109       
     110        for (Relation r : OsmPrimitive.getFilteredList(dataSet.allPrimitives(), Relation.class)) {
     111            final String type = r.get("type");
     112           
     113            if (Constants.TYPE_LENGTHS.equals(type)) {
     114                lenghts.add(r);
     115            } else if (Constants.TYPE_TURNS.equals(type)) {
     116                turns.add(r);
     117            }
     118        }
     119       
     120        final List<Issue> issues = new ArrayList<Issue>();
     121       
     122        final Map<IncomingLanes.Key, IncomingLanes> incomingLanes = new HashMap<IncomingLanes.Key, IncomingLanes>();
     123        issues.addAll(validateLengths(lenghts, incomingLanes));
     124        issues.addAll(validateTurns(turns, incomingLanes));
     125       
     126        for (IncomingLanes lanes : incomingLanes.values()) {
     127            if (lanes.unreferenced() > 0) {
     128                issues.add(Issue.newWarning(Arrays.asList(lanes.key.junction, lanes.key.from),
     129                    tr("{0} lanes are not referenced in any turn-relation.", lanes.unreferenced())));
     130            }
     131        }
     132       
     133        return issues;
     134    }
     135   
     136    private List<Issue> validateLengths(List<Relation> lenghts, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
     137        final List<Issue> issues = new ArrayList<Issue>();
     138       
     139        for (Relation r : lenghts) {
     140            issues.addAll(validateLengths(r, incomingLanes));
     141        }
     142       
     143        return issues;
     144    }
     145   
     146    private List<Issue> validateLengths(Relation r, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
     147        final List<Issue> issues = new ArrayList<Issue>();
     148       
     149        try {
     150            final Node end = Utils.getMemberNode(r, Constants.LENGTHS_ROLE_END);
     151            final Route route = validateLengthsWays(r, end, issues);
     152           
     153            if (route == null) {
     154                return issues;
     155            }
     156           
     157            final List<Double> left = Lane.loadLengths(r, Constants.LENGTHS_KEY_LENGTHS_LEFT, 0);
     158            final List<Double> right = Lane.loadLengths(r, Constants.LENGTHS_KEY_LENGTHS_RIGHT, 0);
     159           
     160            int tooLong = 0;
     161            for (Double l : left) {
     162                if (l > route.getLength()) {
     163                    ++tooLong;
     164                }
     165            }
     166            for (Double l : right) {
     167                if (l > route.getLength()) {
     168                    ++tooLong;
     169                }
     170            }
     171           
     172            if (tooLong > 0) {
     173                issues.add(Issue.newError(r, end, "The lengths-relation specifies " + tooLong
     174                    + " extra-lanes which are longer than its ways."));
     175            }
     176           
     177            putIncomingLanes(route, left, right, incomingLanes);
     178           
     179            return issues;
     180           
     181        } catch (UnexpectedDataException e) {
     182            issues.add(Issue.newError(r, e.getMessage()));
     183            return issues;
     184        }
     185    }
     186   
     187    private void putIncomingLanes(Route route, List<Double> left, List<Double> right,
     188        Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
     189        final Node end = route.getLastSegment().getEnd();
     190        final Way way = route.getLastSegment().getWay();
     191       
     192        final IncomingLanes.Key key = new IncomingLanes.Key(end, way);
     193        final IncomingLanes lanes = new IncomingLanes(key, left.size(), Lane.getRegularCount(way, end), right.size());
     194        final IncomingLanes old = incomingLanes.put(key, lanes);
     195       
     196        if (old != null) {
     197            incomingLanes.put(
     198                key,
     199                new IncomingLanes(key, Math.max(lanes.extraLeft, old.extraLeft), Math.max(lanes.regular, old.regular), Math
     200                    .max(lanes.extraRight, old.extraRight)));
     201        }
     202       
     203        // TODO this tends to produce a bunch of useless errors
     204        // turn lanes really should not span from one junction past another, remove??
     205        //      final double length = route.getLastSegment().getLength();
     206        //      final List<Double> newLeft = reduceLengths(left, length);
     207        //      final List<Double> newRight = new ArrayList<Double>(right.size());
     208        //     
     209        //      if (route.getSegments().size() > 1) {
     210        //          final Route subroute = route.subRoute(0, route.getSegments().size() - 1);
     211        //          putIncomingLanes(subroute, newLeft, newRight, incomingLanes);
     212        //      }
     213    }
     214   
     215    private List<Double> reduceLengths(List<Double> lengths, double length) {
     216        final List<Double> newLengths = new ArrayList<Double>(lengths.size());
     217       
     218        for (double l : lengths) {
     219            if (l > length) {
     220                newLengths.add(l - length);
     221            }
     222        }
     223       
     224        return newLengths;
     225    }
     226   
     227    private Route validateLengthsWays(Relation r, Node end, List<Issue> issues) {
     228        final List<Way> ways = Utils.getMemberWays(r, Constants.LENGTHS_ROLE_WAYS);
     229       
     230        if (ways.isEmpty()) {
     231            issues.add(Issue.newError(r, "A lengths-relation requires at least one member-way with role \""
     232                + Constants.LENGTHS_ROLE_WAYS + "\"."));
     233            return null;
     234        }
     235       
     236        Node current = end;
     237        for (Way w : ways) {
     238            if (!w.isFirstLastNode(current)) {
     239                return orderWays(r, ways, current, issues, "ways", "lengths");
     240            }
     241           
     242            current = Utils.getOppositeEnd(w, current);
     243        }
     244       
     245        return Route.create(ways, end);
     246    }
     247   
     248    private Route orderWays(final Relation r, List<Way> ways, Node end, List<Issue> issues, String role, String type) {
     249        final List<Way> unordered = new ArrayList<Way>(ways);
     250        final List<Way> ordered = new ArrayList<Way>(ways.size());
     251        final Set<Node> ends = new HashSet<Node>(); // to find cycles
     252       
     253        Node current = end;
     254        findNext: while (!unordered.isEmpty()) {
     255            if (!ends.add(current)) {
     256                issues.add(Issue.newError(r, ways, "The " + role + " of the " + type
     257                    + "-relation are unordered (and contain cycles)."));
     258                return null;
     259            }
     260           
     261            Iterator<Way> it = unordered.iterator();
     262            while (it.hasNext()) {
     263                final Way w = it.next();
     264               
     265                if (w.isFirstLastNode(current)) {
     266                    it.remove();
     267                    ordered.add(w);
     268                    current = Utils.getOppositeEnd(w, current);
     269                    continue findNext;
     270                }
     271            }
     272           
     273            issues.add(Issue.newError(r, ways, "The " + role + " of the " + type + "-relation are disconnected."));
     274            return null;
     275        }
     276       
     277        final QuickFix quickFix = new QuickFix(tr("Put the ways in order.")) {
     278            @Override
     279            public boolean perform() {
     280                for (int i = r.getMembersCount() - 1; i >= 0; --i) {
     281                    final RelationMember m = r.getMember(i);
     282                   
     283                    if (m.isWay() && Constants.LENGTHS_ROLE_WAYS.equals(m.getRole())) {
     284                        r.removeMember(i);
     285                    }
     286                }
     287               
     288                for (Way w : ordered) {
     289                    r.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, w));
     290                }
     291               
     292                return true;
     293            }
     294        };
     295       
     296        issues.add(Issue.newError(r, ways, "The ways of the lengths-relation are unordered.", quickFix));
     297       
     298        return Route.create(ordered, end);
     299    }
     300   
     301    private List<Issue> validateTurns(List<Relation> turns, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
     302        final List<Issue> issues = new ArrayList<Issue>();
     303       
     304        for (Relation r : turns) {
     305            issues.addAll(validateTurns(r, incomingLanes));
     306        }
     307       
     308        return issues;
     309    }
     310   
     311    private List<Issue> validateTurns(Relation r, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
     312        final List<Issue> issues = new ArrayList<Issue>();
     313       
     314        try {
     315            final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
     316            final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
     317           
     318            if (from.firstNode().equals(from.lastNode())) {
     319                issues.add(Issue.newError(r, from, "The from-way both starts as well as ends at the via-node."));
     320            }
     321            if (to.firstNode().equals(to.lastNode())) {
     322                issues.add(Issue.newError(r, to, "The to-way both starts as well as ends at the via-node."));
     323            }
     324            if (!issues.isEmpty()) {
     325                return issues;
     326            }
     327           
     328            final Node fromJunctionNode;
     329            final List<RelationMember> viaMembers = Utils.getMembers(r, Constants.TURN_ROLE_VIA);
     330            if (viaMembers.isEmpty()) {
     331                throw UnexpectedDataException.Kind.NO_MEMBER.chuck(Constants.TURN_ROLE_VIA);
     332            } else if (viaMembers.get(0).isWay()) {
     333                final List<Way> vias = Utils.getMemberWays(r, Constants.TURN_ROLE_VIA);
     334               
     335                fromJunctionNode = Utils.lineUp(from, vias.get(0));
     336                Node current = fromJunctionNode;
     337                for (Way via : vias) {
     338                    if (!via.isFirstLastNode(current)) {
     339                        orderWays(r, vias, current, issues, "via-ways", "turns");
     340                        break;
     341                    }
     342                   
     343                    current = Utils.getOppositeEnd(via, current);
     344                }
     345            } else {
     346                final Node via = Utils.getMemberNode(r, Constants.TURN_ROLE_VIA);
     347               
     348                if (!from.isFirstLastNode(via)) {
     349                    issues.add(Issue.newError(r, from, "The from-way does not start or end at the via-node."));
     350                }
     351                if (!to.isFirstLastNode(via)) {
     352                    issues.add(Issue.newError(r, to, "The to-way does not start or end at the via-node."));
     353                }
     354               
     355                fromJunctionNode = via;
     356            }
     357           
     358            if (!issues.isEmpty()) {
     359                return issues;
     360            }
     361            final IncomingLanes lanes = get(incomingLanes, fromJunctionNode, from);
     362           
     363            for (int l : splitInts(r, Constants.TURN_KEY_LANES, issues)) {
     364                if (!lanes.existsRegular(l)) {
     365                    issues.add(Issue.newError(r, tr("Relation references non-existent (regular) lane {0}", l)));
     366                }
     367            }
     368           
     369            for (int l : splitInts(r, Constants.TURN_KEY_EXTRA_LANES, issues)) {
     370                if (!lanes.existsExtra(l)) {
     371                    issues.add(Issue.newError(r, tr("Relation references non-existent extra lane {0}", l)));
     372                }
     373            }
     374           
     375            return issues;
     376        } catch (UnexpectedDataException e) {
     377            issues.add(Issue.newError(r, e.getMessage()));
     378            return issues;
     379        }
     380    }
     381   
     382    private List<Integer> splitInts(Relation r, String key, List<Issue> issues) {
     383        final String ints = r.get(key);
     384       
     385        if (ints == null) {
     386            return Collections.emptyList();
     387        }
     388       
     389        final List<Integer> result = new ArrayList<Integer>();
     390       
     391        for (String s : Constants.SPLIT_PATTERN.split(ints)) {
     392            try {
     393                int i = Integer.parseInt(s.trim());
     394                result.add(Integer.valueOf(i));
     395            } catch (NumberFormatException e) {
     396                issues.add(Issue.newError(r, tr("Integer list \"{0}\" contains unexpected values.", key)));
     397            }
     398        }
     399       
     400        return result;
     401    }
     402   
     403    private IncomingLanes get(Map<IncomingLanes.Key, IncomingLanes> incomingLanes, Node via, Way from) {
     404        final IncomingLanes.Key key = new IncomingLanes.Key(via, from);
     405        final IncomingLanes lanes = incomingLanes.get(key);
     406       
     407        if (lanes == null) {
     408            final IncomingLanes newLanes = new IncomingLanes(key, 0, Lane.getRegularCount(from, via), 0);
     409            incomingLanes.put(key, newLanes);
     410            return newLanes;
     411        } else {
     412            return lanes;
     413        }
     414    }
    415415}
Note: See TracChangeset for help on using the changeset viewer.