Changeset 26154 in osm for applications/editors/josm/plugins
- Timestamp:
- 2011-06-19T16:47:07+02:00 (15 years ago)
- Location:
- applications/editors/josm/plugins/turnlanes
- Files:
-
- 26 edited
-
build.xml (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/CollectionUtils.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/TurnLanesPlugin.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiContainer.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiUtil.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/InteractiveElement.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionGui.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionPane.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/LaneGui.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/Path.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/ReversePathIterator.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/RoadGui.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/State.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/TurnLanesDialog.java (modified) (2 diffs)
-
src/org/openstreetmap/josm/plugins/turnlanes/gui/ValidationPanel.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/Constants.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/Issue.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/Junction.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/Lane.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/ModelContainer.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/Road.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/Route.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/Turn.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/UnexpectedDataException.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/Utils.java (modified) (1 diff)
-
src/org/openstreetmap/josm/plugins/turnlanes/model/Validator.java (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/turnlanes/build.xml
r25607 r26154 33 33 <property name="commit.message" value="Commit message" /> 34 34 <!-- 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" /> 37 36 38 37 <!-- -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/CollectionUtils.java
r25783 r26154 10 10 11 11 public class CollectionUtils { 12 public static <E> Iterable<E> reverse(final List<E> list) {13 return new Iterable<E>() {14 @Override15 public Iterator<E> iterator() {16 final ListIterator<E> it = list.listIterator(list.size());17 18 return new Iterator<E>() {19 @Override20 public boolean hasNext() {21 return it.hasPrevious();22 }23 24 @Override25 public E next() {26 return it.previous();27 }28 29 @Override30 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 } 57 57 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/TurnLanesPlugin.java
r25606 r26154 7 7 8 8 public class TurnLanesPlugin extends Plugin { 9 public TurnLanesPlugin(PluginInformation info) {10 super(info);11 }12 13 @Override14 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {15 if (oldFrame == null && newFrame != null) {16 // there was none before17 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 } 20 20 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiContainer.java
r25783 r26154 24 24 25 25 class 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 unit51 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 pixel111 */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 } 180 180 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiUtil.java
r25783 r26154 17 17 18 18 class 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 intersection39 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() - a47 .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() - a50 .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 } 149 149 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/InteractiveElement.java
r25783 r26154 5 5 6 6 abstract 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 } 45 45 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionGui.java
r25783 r26154 37 37 38 38 class 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 @Override51 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 @Override102 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 @Override135 Type getType() {136 return Type.TURN_CONNECTION;137 }138 139 @Override140 int getZIndex() {141 return 0;142 }143 144 @Override145 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 @Override153 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 @Override160 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 @Override205 public String toString() {206 return "Corner [x1=" + x1 + ", y1=" + y1 + ", cx1=" + cx1 + ", cy1=" + cy1 + ", cx2=" + cx2 + ", cy2=" + cy2207 + ", 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 @Override228 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 corners290 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 } 453 453 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionPane.java
r25908 r26154 27 27 28 28 class 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 @Override54 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 @Override75 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 @Override116 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 @Override165 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 @Override176 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 @Override187 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 @Override197 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 @Override207 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 @Override217 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 @Override228 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 @Override321 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 flag328 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 } 433 433 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/LaneGui.java
r25783 r26154 25 25 26 26 final 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 @Override36 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 @Override57 public boolean contains(Point2D p, State state) {58 return isVisible(state) && circle.contains(p);59 }60 61 @Override62 public Type getType() {63 return Type.INCOMING_CONNECTOR;64 }65 66 @Override67 boolean beginDrag(double x, double y) {68 dragDelta = new Point2D.Double(center.getX() - x, center.getY() - y);69 return true;70 }71 72 @Override73 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 @Override101 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 @Override116 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 @Override157 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 @Override183 public boolean contains(Point2D p, State state) {184 return isVisible(state) && (circle.contains(p) || LaneGui.this.contains(p));185 }186 187 @Override188 public Type getType() {189 return Type.OUTGOING_CONNECTOR;190 }191 192 @Override193 public State activate(State old) {194 return new State.OutgoingActive(LaneGui.this);195 }196 197 @Override198 boolean beginDrag(double x, double y) {199 return circle.contains(x, y);200 }201 202 @Override203 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 @Override224 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 @Override257 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 == null307 || !leftModel.getOutgoingRoadEnd().equals(getModel().getOutgoingRoadEnd()) ? Double.NEGATIVE_INFINITY308 : 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 >= 0322 || 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 >= 0337 || 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 } 364 364 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/Path.java
r25606 r26154 26 26 */ 27 27 abstract 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 @Override47 public int getWindingRule() {48 return WIND_NON_ZERO;49 }50 51 @Override52 public boolean isDone() {53 return done;54 }55 56 @Override57 public void next() {58 if (previous != null && !previous.isDone()) {59 previous.next();60 } else {61 done = true;62 }63 }64 65 @Override66 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 @Override81 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 @Override119 public Point2D getStart() {120 return previous.getStart();121 }122 123 @Override124 public Point2D getEnd() {125 return new Point2D.Double(endX, endY);126 }127 128 @Override129 public double getEndAngle() {130 return angle;131 }132 133 @Override134 public double getLength() {135 return previous.getLength() + length;136 }137 138 @Override139 public Path offset(double ws, double m1, double m2, double we) {140 return offsetInternal(ws, m1, m2, we, angle);141 }142 143 @Override144 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 @Override210 public SimplePathIterator getIterator() {211 return new SimplePathIterator(previous.getIteratorInternal(angle), PathIterator.SEG_LINETO, endX, endY, 0, 0, 0,212 0);213 }214 215 @Override216 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 @Override236 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 @Override249 SimplePathIterator getIteratorInternal(double endAngle) {250 return getIterator();251 }252 }253 254 // TODO curves are still somewhat broken255 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 @Override306 public Point2D getEnd() {307 return new Point2D.Double(endX, endY);308 }309 310 @Override311 public double getEndAngle() {312 return fromAngle + angle;313 }314 315 @Override316 public double getLength() {317 return previous.getLength() + length;318 }319 320 @Override321 public Path offset(double ws, double m1, double m2, double we) {322 return offsetInternal(ws, m1, m2, we, previous.getEndAngle() + angle);323 }324 325 @Override326 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 @Override401 public SimplePathIterator getIterator() {402 return getIteratorInternal(previous.getEndAngle() + angle);403 }404 405 @Override406 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 @Override434 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 angle444 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 @Override454 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 @Override490 public double getEndAngle() {491 if (Double.isNaN(endAngle)) {492 throw new UnsupportedOperationException();493 }494 495 return endAngle;496 }497 498 @Override499 public double getLength() {500 return 0;501 }502 503 @Override504 public Path offset(double ws, double m1, double m2, double we) {505 throw new UnsupportedOperationException();506 }507 508 @Override509 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 @Override515 public SimplePathIterator getIterator() {516 return new SimplePathIterator(PathIterator.SEG_MOVETO, x, y, 0, 0, 0, 0);517 }518 519 @Override520 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 @Override532 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 @Override541 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); 577 577 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ReversePathIterator.java
r25606 r26154 16 16 */ 17 17 class ReversePathIterator implements PathIterator { 18 private static final int[] COUNTS = {19 2, // SEG_MOVETO = 020 2, // SEG_LINETO = 121 4, // SEG_QUADTO = 222 6, // SEG_CUBICTO = 323 0, // SEG_CLOSE = 424 };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 defined33 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 @Override114 public int getWindingRule() {115 return winding;116 }117 118 @Override119 public boolean isDone() {120 return typesIndex >= types.length;121 }122 123 @Override124 public void next() {125 coordsIndex += COUNTS[types[typesIndex]];126 ++typesIndex;127 }128 129 @Override130 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 @Override145 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 } 150 150 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/RoadGui.java
r25783 r26154 41 41 42 42 class 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 @Override56 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 @Override65 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 @Override100 Type getType() {101 return Type.VIA_CONNECTOR;102 }103 104 @Override105 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 @Override131 void paint(Graphics2D g2d, State state) {132 g2d.setStroke(getContainer().getConnectionStroke());133 g2d.setColor(Color.CYAN);134 g2d.draw(line);135 }136 137 @Override138 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 @Override147 State click(State old) {148 end.extend(way);149 return new State.Invalid(old);150 }151 152 @Override153 Type getType() {154 return Type.EXTENDER;155 }156 157 @Override158 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 @Override197 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 @Override220 boolean contains(Point2D p, State state) {221 return isVisible(state) && background.contains(p);222 }223 224 @Override225 Type getType() {226 return Type.LANE_ADDER;227 }228 229 @Override230 int getZIndex() {231 return 2;232 }233 234 @Override235 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 @Override259 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 @Override275 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 @Override313 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 @Override330 public Type getType() {331 return Type.INCOMING_CONNECTOR;332 }333 334 @Override335 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 @Override359 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 to391 // 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 low461 * 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.segments510 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 * container518 .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 centimeter766 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 } 880 880 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/State.java
r25783 r26154 12 12 13 13 interface 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 } 144 144 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/TurnLanesDialog.java
r25783 r26154 18 18 19 19 import org.openstreetmap.josm.actions.JosmAction; 20 import org.openstreetmap.josm.Main; 20 21 import org.openstreetmap.josm.data.SelectionChangedListener; 21 22 import org.openstreetmap.josm.data.osm.DataSet; … … 27 28 28 29 public 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 } 131 144 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ValidationPanel.java
r25606 r26154 25 25 26 26 class 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 @Override38 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 @Override48 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 @Override62 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 @Override102 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 } 139 139 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Constants.java
r25783 r26154 4 4 5 5 public 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 26 26 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Issue.java
r25606 r26154 12 12 13 13 public 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 @Override24 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 } 97 97 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Junction.java
r25783 r26154 10 10 11 11 public 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 painting25 // getTurns(); TODO force this again26 }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 } 88 88 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Lane.java
r25908 r26154 12 12 13 13 public 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 be52 // 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=-183 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 end91 } else {92 return count / 2; // round down in other direction93 }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 lanes142 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 added183 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 } 219 219 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/ModelContainer.java
r25783 r26154 18 18 19 19 public 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 } 237 237 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Road.java
r25783 r26154 20 20 21 21 public 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 end84 */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 r265 * lengths relation266 * @param w267 * the way to check for268 * @param n269 * first or last node of w, determines the direction270 * @return whether the turn lane goes into the direction of n271 */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) : Route296 .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 } 339 339 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Route.java
r25783 r26154 10 10 11 11 public 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 @Override61 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 @Override71 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 } 230 230 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Turn.java
r25783 r26154 17 17 18 18 public 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 done75 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 } 192 192 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/UnexpectedDataException.java
r25908 r26154 6 6 7 7 public 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 } 49 49 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Utils.java
r25908 r26154 20 20 21 21 public 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 a100 * @param b101 * @return the node at which {@code a} and {@code b} are connected102 */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 (in137 * 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 ways146 * ways to be ordered147 * @param nodes148 * start/end nodes149 * @return150 * @throws IllegalArgumentException151 * if the ways can't be ordered152 */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) ? r212 .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 } 225 225 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Validator.java
r25908 r26154 23 23 24 24 public 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 @Override36 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 @Override45 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 " + tooLong174 + " 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), Math200 .max(lanes.extraRight, old.extraRight)));201 }202 203 // TODO this tends to produce a bunch of useless errors204 // 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 cycles252 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 " + type257 + "-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 @Override279 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 } 415 415 }
Note:
See TracChangeset
for help on using the changeset viewer.
