Changeset 26154 in osm for applications/editors/josm/plugins/turnlanes/src/org
- Timestamp:
- 2011-06-19T16:47:07+02:00 (14 years ago)
- Location:
- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes
- Files:
-
- 25 edited
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/CollectionUtils.java
r25783 r26154 10 10 11 11 public class CollectionUtils { 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 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 10 11 12 13 14 15 16 17 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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 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 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 final EastNorth loc = Main.proj.latlon2eastNorth(node.getCoor());131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 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 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 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 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 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 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 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 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 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 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 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 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 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 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 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 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 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 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 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 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 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 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 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 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 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.