Changeset 33085 in osm
- Timestamp:
- 2016-11-25T23:29:59+01:00 (8 years ago)
- Location:
- applications/editors/josm/plugins/turnlanes
- Files:
-
- 2 added
- 27 edited
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/turnlanes/.project
r32286 r33085 16 16 </arguments> 17 17 </buildCommand> 18 <buildCommand> 19 <name>net.sf.eclipsecs.core.CheckstyleBuilder</name> 20 <arguments> 21 </arguments> 22 </buildCommand> 18 23 </buildSpec> 19 24 <natures> 20 25 <nature>org.eclipse.jdt.core.javanature</nature> 26 <nature>net.sf.eclipsecs.core.CheckstyleNature</nature> 21 27 </natures> 22 28 </projectDescription> -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/CollectionUtils.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes; 2 3 … … 9 10 import java.util.Set; 10 11 11 public class CollectionUtils { 12 public final class CollectionUtils { 13 14 private CollectionUtils() { 15 // Hide default constructor for utilities classes 16 } 17 12 18 public static <E> Iterable<E> reverse(final List<E> list) { 13 19 return new Iterable<E>() { … … 15 21 public Iterator<E> iterator() { 16 22 final ListIterator<E> it = list.listIterator(list.size()); 17 23 18 24 return new Iterator<E>() { 19 25 @Override … … 21 27 return it.hasPrevious(); 22 28 } 23 29 24 30 @Override 25 31 public E next() { 26 32 return it.previous(); 27 33 } 28 34 29 35 @Override 30 36 public void remove() { … … 35 41 }; 36 42 } 37 43 38 44 public static <E> Set<E> toSet(Iterable<? extends E> iterable) { 39 45 final Set<E> set = new HashSet<>(); 40 46 41 47 for (E e : iterable) { 42 48 set.add(e); 43 49 } 44 50 45 51 return Collections.unmodifiableSet(set); 46 52 } 47 53 48 54 public static <E> List<E> toList(Iterable<? extends E> iterable) { 49 55 final List<E> list = new ArrayList<>(); 50 56 51 57 for (E e : iterable) { 52 58 list.add(e); 53 59 } 54 60 55 61 return Collections.unmodifiableList(list); 56 62 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/TurnLanesPlugin.java
r26154 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes; 2 3 -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiContainer.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 27 28 static final Color RED = new Color(234, 66, 108); 28 29 static final Color GREEN = new Color(66, 234, 108); 29 30 30 31 private static final GuiContainer EMPTY = new GuiContainer(ModelContainer.empty()); 31 32 32 33 public static GuiContainer empty() { 33 34 return EMPTY; 34 35 } 35 36 36 37 private final ModelContainer mc; 37 38 38 39 private final Point2D translation; 39 40 /** … … 43 44 private final double scale; 44 45 private final double laneWidth; 45 46 46 47 private final Map<Junction, JunctionGui> junctions = new HashMap<>(); 47 48 private final Map<Road, RoadGui> roads = new HashMap<>(); 48 49 49 50 private final Stroke connectionStroke; 50 51 publicGuiContainer(ModelContainer mc) {51 52 GuiContainer(ModelContainer mc) { 52 53 final Point2D origin = avgOrigin(locs(mc.getPrimaryJunctions())); 53 54 54 55 final LatLon originCoor = Main.getProjection().eastNorth2latlon(new EastNorth(origin.getX(), origin.getY())); 55 56 final LatLon relCoor = Main.getProjection().eastNorth2latlon( 56 57 new EastNorth(origin.getX() + 1, origin.getY() + 1)); 57 58 58 59 // meters per source unit 59 60 final double mpsu = relCoor.greatCircleDistance(originCoor) / sqrt(2); 60 61 61 62 this.mc = mc; 62 63 this.translation = new Point2D.Double(-origin.getX(), -origin.getY()); … … 64 65 this.scale = mpsu / mpp; 65 66 this.laneWidth = 2 / mpp; 66 67 67 68 this.connectionStroke = new BasicStroke((float) (laneWidth / 4), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); 68 69 69 70 for (Junction j : mc.getPrimaryJunctions()) { 70 71 getGui(j); 71 72 } 72 73 } 73 74 74 75 private static Point2D avgOrigin(List<Point2D> locs) { 75 76 if (locs.isEmpty()) { 76 77 return new Point2D.Double(0, 0); 77 78 } 78 79 79 80 double x = 0; 80 81 double y = 0; 81 82 82 83 for (Point2D l : locs) { 83 84 x += l.getX(); 84 85 y += l.getY(); 85 86 } 86 87 87 88 return new Point2D.Double(x / locs.size(), y / locs.size()); 88 89 } 89 90 90 91 public JunctionGui getGui(Junction j) { 91 92 final JunctionGui existing = junctions.get(j); … … 93 94 return existing; 94 95 } 95 96 96 97 return new JunctionGui(this, j); 97 98 } 98 99 99 100 void register(JunctionGui j) { 100 101 if (junctions.put(j.getModel(), j) != null) { … … 102 103 } 103 104 } 104 105 105 106 public RoadGui getGui(Road r) { 106 107 final RoadGui gui = roads.get(r); 107 108 108 109 if (gui == null) { 109 110 final RoadGui newGui = new RoadGui(this, r); … … 111 112 return newGui; 112 113 } 113 114 114 115 return gui; 115 116 } 116 117 117 118 Point2D translateAndScale(Point2D loc) { 118 119 return new Point2D.Double((loc.getX() + translation.getX()) * scale, (loc.getY() + translation.getY()) * scale); 119 120 } 120 121 121 122 /** 122 123 * @return meters per pixel … … 125 126 return mpp; 126 127 } 127 128 128 129 public double getScale() { 129 130 return scale; 130 131 } 131 132 132 133 public double getLaneWidth() { 133 134 return laneWidth; 134 135 } 135 136 136 137 public Stroke getConnectionStroke() { 137 138 return connectionStroke; 138 139 } 139 140 140 141 public LaneGui getGui(Lane lane) { 141 142 final RoadGui roadGui = roads.get(lane.getRoad()); 142 143 143 144 for (LaneGui l : roadGui.getLanes()) { 144 145 if (l.getModel().equals(lane)) { … … 146 147 } 147 148 } 148 149 149 150 throw new IllegalArgumentException(tr("No such lane.")); 150 151 } 151 152 152 153 public ModelContainer getModel() { 153 154 return mc; 154 155 } 155 156 156 157 public Rectangle2D getBounds() { 157 158 if (isEmpty()) { 158 159 return new Rectangle2D.Double(-1, -1, 2, 2); 159 160 } 160 161 161 162 final List<Junction> primaries = new ArrayList<>(mc.getPrimaryJunctions()); 162 163 final List<Double> top = new ArrayList<>(); … … 164 165 final List<Double> right = new ArrayList<>(); 165 166 final List<Double> bottom = new ArrayList<>(); 166 167 167 168 for (Junction j : primaries) { 168 169 final JunctionGui g = getGui(j); 169 170 final Rectangle2D b = g.getBounds(); 170 171 171 172 top.add(b.getMinY()); 172 173 left.add(b.getMinX()); … … 174 175 bottom.add(b.getMaxY()); 175 176 } 176 177 177 178 final double t = Collections.min(top); 178 179 final double l = Collections.min(left); 179 180 final double r = Collections.max(right); 180 181 final double b = Collections.max(bottom); 181 182 182 183 return new Rectangle2D.Double(l, t, r - l, b - t); 183 184 } 184 185 185 186 public GuiContainer recalculate() { 186 187 return new GuiContainer(mc.recalculate()); 187 188 } 188 189 189 190 public Iterable<RoadGui> getRoads() { 190 191 return roads.values(); 191 192 } 192 193 193 194 public Iterable<JunctionGui> getJunctions() { 194 195 return junctions.values(); 195 196 } 196 197 197 198 public boolean isEmpty() { 198 199 return mc.isEmpty(); -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiUtil.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 16 17 import org.openstreetmap.josm.plugins.turnlanes.model.Junction; 17 18 18 class GuiUtil { 19 final class GuiUtil { 20 21 private GuiUtil() { 22 // Hide default constructor for utilities classes 23 } 24 19 25 static double normalize(double a) { 20 26 while (a < 0) { … … 26 32 return a; 27 33 } 28 34 29 35 // control point factor for curves (circle segment of angle a) 30 36 static double cpf(double a, double scale) { 31 37 return 4.0 / 3 * Math.tan(min(abs(a), PI - 0.001) / 4) * scale; 32 38 } 33 39 34 40 static Point2D intersection(Line2D a, Line2D b) { 35 41 final double aa = GuiUtil.angle(a); 36 42 final double ab = GuiUtil.angle(b); 37 43 38 44 // less than 1/2 degree => no intersection 39 45 if (Math.abs(Math.PI - abs(minAngleDiff(aa, ab))) < PI / 360) { 40 46 return null; 41 47 } 42 48 43 49 final double d = (a.getX1() - a.getX2()) * (b.getY1() - b.getY2()) - (a.getY1() - a.getY2()) 44 50 * (b.getX1() - b.getX2()); 45 51 46 52 final double x = ((b.getX1() - b.getX2()) * (a.getX1() * a.getY2() - a.getY1() * a.getX2()) - (a.getX1() - a 47 53 .getX2()) * (b.getX1() * b.getY2() - b.getY1() * b.getX2())) … … 50 56 .getY2()) * (b.getX1() * b.getY2() - b.getY1() * b.getX2())) 51 57 / d; 52 58 53 59 return new Point2D.Double(x, y); 54 60 } 55 61 56 62 static Point2D closest(Line2D l, Point2D p) { 57 63 final Point2D lv = vector(l.getP1(), l.getP2()); 58 64 final double numerator = dot(vector(l.getP1(), p), lv); 59 65 60 66 if (numerator < 0) { 61 67 return l.getP1(); 62 68 } 63 69 64 70 final double denominator = dot(lv, lv); 65 71 if (numerator >= denominator) { 66 72 return l.getP2(); 67 73 } 68 74 69 75 final double r = numerator / denominator; 70 76 return new Point2D.Double(l.getX1() + r * lv.getX(), l.getY1() + r * lv.getY()); 71 77 } 72 78 73 79 private static double dot(Point2D a, Point2D b) { 74 80 return a.getX() * b.getX() + a.getY() * b.getY(); 75 81 } 76 82 77 83 private static Point2D vector(Point2D from, Point2D to) { 78 84 return new Point2D.Double(to.getX() - from.getX(), to.getY() - from.getY()); 79 85 } 80 86 81 87 public static double angle(Point2D from, Point2D to) { 82 88 final double dx = to.getX() - from.getX(); 83 89 final double dy = -(to.getY() - from.getY()); 84 90 85 91 return normalize(Math.atan2(dy, dx)); 86 92 } 87 93 88 94 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) //95 return new Point2D.Double( 96 p.getX() + r * Math.cos(a), 97 p.getY() - r * Math.sin(a) 92 98 ); 93 99 } 94 100 95 101 public static Line2D relativeLine(Line2D l, double r, double a) { 96 102 final double dx = r * Math.cos(a); 97 103 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 return new Line2D.Double( 106 l.getX1() + dx, 107 l.getY1() + dy, 108 l.getX2() + dx, 109 l.getY2() + dy 104 110 ); 105 111 } 106 112 107 113 public static double angle(Line2D l) { 108 114 return angle(l.getP1(), l.getP2()); 109 115 } 110 116 111 117 public static double minAngleDiff(double a1, double a2) { 112 118 final double d = normalize(a2 - a1); 113 119 114 120 return d > Math.PI ? -(2 * Math.PI - d) : d; 115 121 } 116 117 public static finalPoint2D middle(Point2D a, Point2D b) {122 123 public static Point2D middle(Point2D a, Point2D b) { 118 124 return relativePoint(a, a.distance(b) / 2, angle(a, b)); 119 125 } 120 121 public static finalPoint2D middle(Line2D l) {126 127 public static Point2D middle(Line2D l) { 122 128 return middle(l.getP1(), l.getP2()); 123 129 } 124 130 125 131 public static Line2D line(Point2D p, double a) { 126 132 return new Line2D.Double(p, relativePoint(p, 1, a)); 127 133 } 128 134 129 135 public static Point2D loc(Node node) { 130 136 final EastNorth loc = Main.getProjection().latlon2eastNorth(node.getCoor()); 131 137 return new Point2D.Double(loc.getX(), -loc.getY()); 132 138 } 133 139 134 140 public static List<Point2D> locs(Iterable<Junction> junctions) { 135 141 final List<Point2D> locs = new ArrayList<>(); 136 142 137 143 for (Junction j : junctions) { 138 144 locs.add(loc(j.getNode())); 139 145 } 140 146 141 147 return locs; 142 148 } 143 149 144 150 static void area(Path2D area, Path inner, Path outer) { 145 151 area.append(inner.getIterator(), false); -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/InteractiveElement.java
r26154 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 13 14 Type VIA_CONNECTOR = new Type() {}; 14 15 } 15 16 16 17 public void paintBackground(Graphics2D g2d, State state) {} 17 18 18 19 abstract void paint(Graphics2D g2d, State state); 19 20 20 21 abstract boolean contains(Point2D p, State state); 21 22 22 23 abstract Type getType(); 23 24 24 25 State activate(State old) { 25 26 return old; 26 27 } 27 28 28 29 boolean beginDrag(double x, double y) { 29 30 return false; 30 31 } 31 32 32 33 State drag(double x, double y, InteractiveElement target, State old) { 33 34 return old; 34 35 } 35 36 36 37 State drop(double x, double y, InteractiveElement target, State old) { 37 38 return old; 38 39 } 39 40 40 41 abstract int getZIndex(); 41 42 42 43 State click(State old) { 43 44 return old; -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionGui.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 39 40 private final class TurnConnection extends InteractiveElement { 40 41 private final Turn turn; 41 42 42 43 private Point2D dragBegin; 43 44 private double dragOffsetX = 0; 44 45 private double dragOffsetY = 0; 45 46 publicTurnConnection(Turn turn) {46 47 TurnConnection(Turn turn) { 47 48 this.turn = turn; 48 49 } 49 50 50 51 @Override 51 52 void paint(Graphics2D g2d, State state) { … … 58 59 } 59 60 } 60 61 61 62 private Path2D getPath() { 62 63 final Path2D path = new Path2D.Double(); 63 64 64 65 final LaneGui laneGui = getContainer().getGui(turn.getFrom()); 65 66 final RoadGui roadGui = getContainer().getGui(turn.getTo().getRoad()); 66 67 67 68 path.moveTo(laneGui.outgoing.getCenter().getX(), laneGui.outgoing.getCenter().getY()); 68 69 69 70 Junction j = laneGui.getModel().getOutgoingJunction(); 70 71 for (Road v : turn.getVia()) { … … 77 78 j = v.getFromEnd().getJunction(); 78 79 } 79 80 80 81 path.append(it, true); 81 82 } 82 83 83 84 path.lineTo(roadGui.getConnector(turn.getTo()).getCenter().getX(), roadGui.getConnector(turn.getTo()).getCenter() 84 85 .getY()); 85 86 86 87 return path; 87 88 } 88 89 89 90 private boolean isVisible(State state) { 90 91 if (state instanceof State.AllTurns) { … … 95 96 return turn.getTo().equals(((State.IncomingActive) state).getRoadEnd()); 96 97 } 97 98 98 99 return false; 99 100 } 100 101 101 102 @Override 102 103 boolean contains(Point2D p, State state) { … … 104 105 return false; 105 106 } 106 107 107 108 final PathIterator it = new FlatteningPathIterator(getPath().getPathIterator(null), 0.05 / getContainer() 108 109 .getMpp()); … … 113 114 if (it.currentSegment(coords) == PathIterator.SEG_LINETO) { 114 115 final Point2D closest = closest(new Line2D.Double(lastX, lastY, coords[0], coords[1]), p); 115 116 116 117 if (p.distance(closest) <= strokeWidth() / 2) { 117 118 return true; 118 119 } 119 120 } 120 121 121 122 lastX = coords[0]; 122 123 lastY = coords[1]; 123 124 it.next(); 124 125 } 125 126 126 127 return false; 127 128 } 128 129 129 130 private double strokeWidth() { 130 131 final BasicStroke stroke = (BasicStroke) getContainer().getConnectionStroke(); 131 132 return stroke.getLineWidth(); 132 133 } 133 134 134 135 @Override 135 136 Type getType() { 136 137 return Type.TURN_CONNECTION; 137 138 } 138 139 139 140 @Override 140 141 int getZIndex() { 141 142 return 0; 142 143 } 143 144 144 145 @Override 145 146 boolean beginDrag(double x, double y) { … … 149 150 return true; 150 151 } 151 152 152 153 @Override 153 154 State drag(double x, double y, InteractiveElement target, State old) { … … 156 157 return old; 157 158 } 158 159 159 160 @Override 160 161 State drop(double x, double y, InteractiveElement target, State old) { 161 162 drag(x, y, target, old); 162 163 163 164 if (isRemoveDragOffset()) { 164 165 turn.remove(); 165 166 } 166 167 167 168 dragBegin = null; 168 169 dragOffsetX = 0; … … 170 171 return new State.Dirty(old); 171 172 } 172 173 173 174 private boolean isRemoveDragOffset() { 174 175 final double r = getContainer().getGui(turn.getFrom().getRoad()).connectorRadius; … … 177 178 } 178 179 } 179 180 180 181 private final class Corner { 181 182 final double x1; 182 183 final double y1; 183 184 184 185 final double cx1; 185 186 final double cy1; 186 187 187 188 final double cx2; 188 189 final double cy2; 189 190 190 191 final double x2; 191 192 final double y2; 192 193 publicCorner(Point2D c1, Point2D cp1, Point2D cp2, Point2D c2) {193 194 Corner(Point2D c1, Point2D cp1, Point2D cp2, Point2D c2) { 194 195 this.x1 = c1.getX(); 195 196 this.y1 = c1.getY(); … … 201 202 this.y2 = c2.getY(); 202 203 } 203 204 204 205 @Override 205 206 public String toString() { … … 208 209 } 209 210 } 210 211 211 212 private final class Linkage implements Comparable<Linkage> { 212 213 final RoadGui roadGui; 213 214 final Road.End roadEnd; 214 215 final double angle; 215 216 216 217 double lTrim; 217 218 double rTrim; 218 219 publicLinkage(Road.End roadEnd) {219 220 Linkage(Road.End roadEnd) { 220 221 this.roadGui = getContainer().getGui(roadEnd.getRoad()); 221 222 this.roadEnd = roadEnd; 222 223 this.angle = normalize(roadGui.getAngle(roadEnd) + PI); 223 224 224 225 roads.put(angle, this); 225 226 } 226 227 227 228 @Override 228 229 public int compareTo(Linkage o) { 229 230 return Double.compare(angle, o.angle); 230 231 } 231 232 232 233 public void trimLeft(Linkage right) { 233 234 right.trimRight(this); 234 235 235 236 final Line2D leftCurb = roadGui.getLeftCurb(roadEnd); 236 237 final Line2D rightCurb = right.roadGui.getRightCurb(right.roadEnd); 237 238 238 239 final double leftAngle = angle(leftCurb); 239 240 final double rightAngle = angle(rightCurb); 240 241 241 242 final Point2D isect; 242 243 if (abs(PI - normalize(rightAngle - leftAngle)) > PI / 12) { … … 245 246 isect = GuiUtil.relativePoint(leftCurb.getP1(), roadGui.getWidth(roadEnd) / 2, angle); 246 247 } 247 248 248 249 if (Math.abs(leftAngle - angle(leftCurb.getP1(), isect)) < 0.1) { 249 250 lTrim = leftCurb.getP1().distance(isect); 250 251 } 251 252 } 252 253 253 254 private void trimRight(Linkage left) { 254 255 final Line2D rightCurb = roadGui.getRightCurb(roadEnd); 255 256 final Line2D leftCurb = left.roadGui.getLeftCurb(left.roadEnd); 256 257 257 258 final double rightAngle = angle(rightCurb); 258 259 final double leftAngle = angle(leftCurb); 259 260 260 261 final Point2D isect; 261 262 if (abs(PI - normalize(rightAngle - leftAngle)) > PI / 12) { … … 264 265 isect = GuiUtil.relativePoint(rightCurb.getP1(), roadGui.getWidth(roadEnd) / 2, angle); 265 266 } 266 267 267 268 if (Math.abs(rightAngle - angle(rightCurb.getP1(), isect)) < 0.1) { 268 269 rTrim = rightCurb.getP1().distance(isect); 269 270 } 270 271 } 271 272 272 273 public void trimAdjust() { 273 274 final double MAX_TAN = tan(PI / 2 - MAX_ANGLE); 274 275 275 276 final double sin = roadGui.getWidth(roadEnd); 276 277 final double cos = abs(lTrim - rTrim); 277 278 final double tan = sin / cos; 278 279 279 280 if (tan < MAX_TAN) { 280 281 lTrim = max(lTrim, rTrim - sin / MAX_TAN); 281 282 rTrim = max(rTrim, lTrim - sin / MAX_TAN); 282 283 } 283 284 284 285 lTrim += container.getLaneWidth() / 2; 285 286 rTrim += container.getLaneWidth() / 2; 286 287 } 287 288 } 288 289 289 290 // max angle between corners 290 291 private static final double MAX_ANGLE = Math.toRadians(30); 291 292 292 293 private final GuiContainer container; 293 294 private final Junction junction; 294 295 295 296 final double x; 296 297 final double y; 297 298 298 299 private final NavigableMap<Double, Linkage> roads = new TreeMap<>(); 299 300 300 301 private final Path2D area = new Path2D.Double(); 301 302 publicJunctionGui(GuiContainer container, Junction j) {302 303 JunctionGui(GuiContainer container, Junction j) { 303 304 this.container = container; 304 305 this.junction = j; 305 306 306 307 container.register(this); 307 308 308 309 final Point2D loc = container.translateAndScale(loc(j.getNode())); 309 310 this.x = loc.getX(); 310 311 this.y = loc.getY(); 311 312 312 313 final Set<Road> done = new HashSet<>(); 313 314 for (Road r : j.getRoads()) { 314 315 if (!done.contains(r)) { 315 316 done.add(r); 316 317 317 318 if (r.getFromEnd().getJunction().equals(j)) { 318 319 new Linkage(r.getFromEnd()); … … 323 324 } 324 325 } 325 326 326 327 recalculate(); 327 328 } 328 329 329 330 void recalculate() { 330 331 for (Linkage l : roads.values()) { … … 332 333 l.rTrim = 0; 333 334 } 334 335 335 336 area.reset(); 336 337 if (roads.size() < 2) { 337 338 return; 338 339 } 339 340 340 341 Linkage last = roads.lastEntry().getValue(); 341 342 for (Linkage l : roads.values()) { … … 346 347 l.trimAdjust(); 347 348 } 348 349 349 350 boolean first = true; 350 351 for (Corner c : corners()) { … … 355 356 area.lineTo(c.x1, c.y1); 356 357 } 357 358 358 359 area.curveTo(c.cx1, c.cy1, c.cx2, c.cy2, c.x2, c.y2); 359 360 } 360 361 361 362 area.closePath(); 362 363 } 363 364 364 365 private Iterable<Corner> corners() { 365 366 final List<Corner> result = new ArrayList<>(roads.size()); 366 367 367 368 Linkage last = roads.lastEntry().getValue(); 368 369 for (Linkage l : roads.values()) { … … 370 371 last = l; 371 372 } 372 373 373 374 return result; 374 375 } 375 376 376 377 private Corner corner(Linkage right, Linkage left) { 377 378 final Line2D rightCurb = right.roadGui.getRightCurb(right.roadEnd); 378 379 final Line2D leftCurb = left.roadGui.getLeftCurb(left.roadEnd); 379 380 380 381 final double rightAngle = angle(rightCurb); 381 382 final double leftAngle = angle(leftCurb); 382 383 383 384 final double delta = normalize(leftAngle - rightAngle); 384 385 385 386 final boolean wide = delta > PI; 386 387 final double a = wide ? max(0, delta - (PI + 2 * MAX_ANGLE)) : delta; 387 388 388 389 final double cpf1 = cpf(a, container.getLaneWidth() / 2 + (wide ? right.roadGui.getWidth(right.roadEnd) : 0)); 389 390 final double cpf2 = cpf(a, container.getLaneWidth() / 2 + (wide ? left.roadGui.getWidth(left.roadEnd) : 0)); 390 391 391 392 final Point2D c1 = relativePoint(rightCurb.getP1(), cpf1, right.angle + PI); 392 393 final Point2D c2 = relativePoint(leftCurb.getP1(), cpf2, left.angle + PI); 393 394 394 395 return new Corner(rightCurb.getP1(), c1, c2, leftCurb.getP1()); 395 396 } 396 397 397 398 public Set<RoadGui> getRoads() { 398 399 final Set<RoadGui> result = new HashSet<>(); 399 400 400 401 for (Linkage l : roads.values()) { 401 402 result.add(l.roadGui); 402 403 } 403 404 404 405 return Collections.unmodifiableSet(result); 405 406 } 406 407 407 408 double getLeftTrim(Road.End end) { 408 409 return getLinkage(end).lTrim; 409 410 } 410 411 411 412 private Linkage getLinkage(Road.End end) { 412 413 final double a = normalize(getContainer().getGui(end.getRoad()).getAngle(end) + PI); … … 414 415 return e != null ? e.getValue() : null; 415 416 } 416 417 417 418 double getRightTrim(Road.End end) { 418 419 return getLinkage(end).rTrim; 419 420 } 420 421 421 422 Point2D getPoint() { 422 423 return new Point2D.Double(x, y); 423 424 } 424 425 425 426 public GuiContainer getContainer() { 426 427 return container; 427 428 } 428 429 429 430 public Junction getModel() { 430 431 return junction; 431 432 } 432 433 433 434 public List<InteractiveElement> paint(Graphics2D g2d) { 434 435 g2d.setColor(new Color(96, 96, 96)); 435 436 g2d.fill(area); 436 437 437 438 final List<InteractiveElement> result = new ArrayList<>(); 438 439 439 440 if (getModel().isPrimary()) { 440 441 for (Road.End r : new HashSet<>(getModel().getRoadEnds())) { … … 444 445 } 445 446 } 446 447 447 448 return result; 448 449 } 449 450 450 451 public Rectangle2D getBounds() { 451 452 return area.getBounds2D(); -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionPane.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 39 40 private int originY; 40 41 private int button; 41 42 43 @Override 42 44 public void mousePressed(MouseEvent e) { 43 45 JunctionPane.this.requestFocus(); 44 46 45 47 button = e.getButton(); 46 48 47 49 if (button == MouseEvent.BUTTON1) { 48 50 final Point2D mouse = translateMouseCoords(e); … … 55 57 } 56 58 } 57 59 58 60 originX = e.getX(); 59 61 originY = e.getY(); 60 62 } 61 63 62 64 @Override 63 65 public void mouseReleased(MouseEvent e) { … … 66 68 setState(dragging.drop(mouse.getX(), mouse.getY(), dropTarget(mouse), state)); 67 69 } 68 70 69 71 dragging = null; 70 72 repaint(); 71 73 } 72 74 73 75 private InteractiveElement dropTarget(Point2D mouse) { 74 76 for (InteractiveElement ie : interactives()) { … … 77 79 } 78 80 } 79 81 80 82 return null; 81 83 } 82 84 83 85 @Override 84 86 public void mouseClicked(MouseEvent e) { … … 93 95 } 94 96 } 95 97 98 @Override 96 99 public void mouseDragged(MouseEvent e) { 97 100 if (button == MouseEvent.BUTTON1) { 98 101 final Point2D mouse = translateMouseCoords(e); 99 102 100 103 if (dragging == null) { 101 104 final Point2D origin = translateCoords(originX, originY); … … 105 108 dragging = ie; 106 109 } 107 110 108 111 break; 109 112 } 110 113 } 111 114 } 112 115 113 116 if (dragging != null) { 114 117 setState(dragging.drag(mouse.getX(), mouse.getY(), dropTarget(mouse), state)); … … 116 119 } else if (button == MouseEvent.BUTTON3) { 117 120 translate(e.getX() - originX, e.getY() - originY); 118 121 119 122 originX = e.getX(); 120 123 originY = e.getY(); 121 124 } 122 125 } 123 126 124 127 @Override 125 128 public void mouseWheelMoved(MouseWheelEvent e) { 126 129 scale(e.getX(), e.getY(), Math.pow(0.8, e.getWheelRotation())); 127 130 } 128 131 129 132 private Point2D translateMouseCoords(MouseEvent e) { 130 133 return translateCoords(e.getX(), e.getY()); 131 134 } 132 135 133 136 private Point2D translateCoords(int x, int y) { 134 137 final double c = Math.cos(-rotation); 135 138 final double s = Math.sin(-rotation); 136 139 137 140 final double x2 = -translationX + x / scale; 138 141 final double y2 = -translationY + y / scale; 139 142 140 143 return new Point2D.Double(x2 * c - y2 * s, x2 * s + y2 * c); 141 144 } 142 145 } 143 146 144 147 private static final long serialVersionUID = 6917061040674799271L; 145 148 146 149 private static final Color TRANSPARENT = new Color(0, 0, 0, 0); 147 150 148 151 private final MouseInputProcessor mip = new MouseInputProcessor(); 149 152 150 153 private GuiContainer container; 151 154 152 155 private final JLabel error = new JLabel(""); 153 156 154 157 private int width = 0; 155 158 private int height = 0; … … 161 164 private BufferedImage passive; 162 165 private BufferedImage interactive; 163 166 164 167 private final NavigableMap<Integer, List<InteractiveElement>> interactives = new TreeMap<>(); 165 168 private State state; 166 169 private InteractiveElement dragging; 167 168 publicJunctionPane(GuiContainer container) {170 171 JunctionPane(GuiContainer container) { 169 172 setJunction(container); 170 173 171 174 setLayout(new GridBagLayout()); 172 175 error.setOpaque(false); 173 176 add(error, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, 174 177 new Insets(8, 8, 8, 8), 0, 0)); 175 178 176 179 setFocusable(true); 177 180 178 181 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), "refresh"); 179 182 getActionMap().put("refresh", new AbstractAction() { 180 183 private static final long serialVersionUID = 1L; 181 184 182 185 @Override 183 186 public void actionPerformed(ActionEvent e) { … … 185 188 } 186 189 }); 187 190 188 191 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0), "zoomIn"); 189 192 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "zoomIn"); 190 193 getActionMap().put("zoomIn", new AbstractAction() { 191 194 private static final long serialVersionUID = 1L; 192 195 193 196 @Override 194 197 public void actionPerformed(ActionEvent e) { … … 196 199 } 197 200 }); 198 201 199 202 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0), "zoomOut"); 200 203 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "zoomOut"); 201 204 getActionMap().put("zoomOut", new AbstractAction() { 202 205 private static final long serialVersionUID = 1L; 203 206 204 207 @Override 205 208 public void actionPerformed(ActionEvent e) { … … 207 210 } 208 211 }); 209 212 210 213 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "center"); 211 214 getActionMap().put("center", new AbstractAction() { 212 215 private static final long serialVersionUID = 1L; 213 216 214 217 @Override 215 218 public void actionPerformed(ActionEvent e) { … … 217 220 } 218 221 }); 219 222 220 223 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK), "toggleAllTurns"); 221 224 getActionMap().put("toggleAllTurns", new AbstractAction() { 222 225 private static final long serialVersionUID = 1L; 223 226 224 227 @Override 225 228 public void actionPerformed(ActionEvent e) { … … 227 230 } 228 231 }); 229 232 230 233 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_L, InputEvent.CTRL_DOWN_MASK), "rotateLeft"); 231 234 getActionMap().put("rotateLeft", new AbstractAction() { 232 235 private static final long serialVersionUID = 1L; 233 236 234 237 @Override 235 238 public void actionPerformed(ActionEvent e) { … … 238 241 } 239 242 }); 240 243 241 244 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK), "rotateRight"); 242 245 getActionMap().put("rotateRight", new AbstractAction() { 243 246 private static final long serialVersionUID = 1L; 244 247 245 248 @Override 246 249 public void actionPerformed(ActionEvent e) { … … 249 252 } 250 253 }); 251 254 252 255 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); 253 256 getActionMap().put("delete", new AbstractAction() { 254 257 private static final long serialVersionUID = 1L; 255 258 256 259 @Override 257 260 public void actionPerformed(ActionEvent e) { … … 260 263 }); 261 264 } 262 265 263 266 public void setJunction(GuiContainer container) { 264 267 removeMouseListener(mip); … … 267 270 this.interactives.clear(); 268 271 this.dragging = null; 269 272 270 273 this.container = container; 271 274 center(); 272 275 setState(new State.Invalid(new State.Default())); 273 276 274 277 addMouseListener(mip); 275 278 addMouseMotionListener(mip); 276 279 addMouseWheelListener(mip); 277 280 } 278 281 279 282 private void center() { 280 283 final Rectangle2D bounds = container.getBounds(); 281 284 282 285 rotation = 0; 283 286 284 287 scale = Math.min(getHeight() / 2 / bounds.getHeight(), getWidth() / 2 / bounds.getWidth()); 285 288 286 289 translationX = -bounds.getCenterX(); 287 290 translationY = -bounds.getCenterY(); 288 291 289 292 translate(getWidth() / 2d, getHeight() / 2d); 290 293 } 291 294 292 295 private void toggleAllTurns() { 293 296 if (state instanceof State.AllTurns) { … … 297 300 } 298 301 } 299 302 300 303 private void setState(State state) { 301 304 error.setText(""); 302 305 303 306 if (state instanceof State.AllTurns) { 304 307 dirty = true; … … 307 310 dirty = true; 308 311 setState(((State.Invalid) state).unwrap()); 309 312 310 313 try { 311 314 final GuiContainer old = container; 312 315 313 316 container = container.recalculate(); 314 317 315 318 if (old.isEmpty() != container.isEmpty()) { 316 319 center(); … … 327 330 this.state = state.carryOver(container); 328 331 } 329 332 330 333 repaint(); 331 334 } 332 335 333 336 private void displayError(UnexpectedDataException e) { 334 337 if (e.getKind() == UnexpectedDataException.Kind.MISSING_TAG 335 338 && UnexpectedDataException.Kind.MISSING_TAG.format("lanes").equals(e.getMessage())) { 336 339 337 340 error.setText(tr("<html>The number of lanes is not specified for one or more roads;" 338 341 + " please add missing lanes tags.</html>")); … … 341 344 } 342 345 } 343 346 344 347 private void displayError(RuntimeException e) { 345 348 error.setText(tr("<html>An error occurred while constructing the model." … … 347 350 + "<br><br>Error: {0}</html>", e.getMessage())); 348 351 } 349 352 350 353 void scale(int x, int y, double scale) { 351 354 this.scale *= scale; 352 355 353 356 final double w = getWidth(); 354 357 final double h = getHeight(); 355 358 356 359 translationX -= (w * (scale - 1)) / (2 * this.scale); 357 360 translationY -= (h * (scale - 1)) / (2 * this.scale); 358 361 359 362 dirty = true; 360 363 repaint(); 361 364 } 362 365 363 366 void scale(double scale) { 364 367 scale(getWidth() / 2, getHeight() / 2, scale); 365 368 } 366 369 367 370 void translate(double x, double y) { 368 371 translationX += x / scale; 369 372 translationY += y / scale; 370 373 371 374 dirty = true; 372 375 repaint(); 373 376 } 374 377 375 378 @Override 376 379 protected void paintComponent(Graphics g) { … … 379 382 width = getWidth(); 380 383 height = getHeight(); 381 384 382 385 // translate already set dirty flag 383 386 passive = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); 384 387 interactive = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 385 388 } 386 389 387 390 if (dirty) { 388 391 paintPassive((Graphics2D) passive.getGraphics()); … … 390 393 } 391 394 paintInteractive((Graphics2D) interactive.getGraphics()); 392 395 393 396 final Graphics2D g2d = (Graphics2D) g; 394 397 395 398 g2d.drawImage(passive, 0, 0, getWidth(), getHeight(), null); 396 399 g2d.drawImage(interactive, 0, 0, getWidth(), getHeight(), null); 397 400 398 401 paintChildren(g); 399 402 } 400 403 401 404 private void paintInteractive(Graphics2D g2d) { 402 405 g2d.setBackground(TRANSPARENT); … … 405 408 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 406 409 g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); 407 410 408 411 g2d.scale(scale, scale); 409 412 g2d.translate(translationX, translationY); 410 413 g2d.rotate(rotation); 411 414 412 415 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.7f)); 413 416 414 417 for (Map.Entry<Integer, List<InteractiveElement>> e : interactives.entrySet()) { 415 418 for (InteractiveElement ie : e.getValue()) { … … 421 424 } 422 425 } 423 426 424 427 private List<InteractiveElement> interactives() { 425 428 final List<InteractiveElement> result = new ArrayList<>(); 426 429 427 430 for (List<InteractiveElement> ies : interactives.descendingMap().values()) { 428 431 result.addAll(ies); 429 432 } 430 433 431 434 return result; 432 435 } 433 436 434 437 private void paintPassive(Graphics2D g2d) { 435 438 interactives.clear(); 436 439 437 440 g2d.setBackground(new Color(100, 160, 240)); 438 441 g2d.clearRect(0, 0, getWidth(), getHeight()); 439 442 440 443 g2d.scale(scale, scale); 441 444 g2d.translate(translationX, translationY); 442 445 g2d.rotate(rotation); 443 446 444 447 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 445 448 446 449 g2d.setColor(Color.GRAY); 447 450 for (RoadGui r : container.getRoads()) { 448 451 addAllInteractives(r.paint(g2d)); 449 452 } 450 453 451 454 for (JunctionGui j : container.getJunctions()) { 452 455 addAllInteractives(j.paint(g2d)); … … 454 457 } 455 458 } 456 459 457 460 private void addAllInteractives(List<InteractiveElement> ies) { 458 461 for (InteractiveElement ie : ies) { 459 462 final List<InteractiveElement> existing = interactives.get(ie.getZIndex()); 460 463 461 464 final List<InteractiveElement> list; 462 465 if (existing == null) { … … 466 469 list = existing; 467 470 } 468 471 469 472 list.add(ie); 470 473 } 471 474 } 472 475 473 476 static void dot(Graphics2D g2d, Point2D p, double r, Color c) { 474 477 final Color old = g2d.getColor(); 475 478 476 479 g2d.setColor(c); 477 480 g2d.fill(new Ellipse2D.Double(p.getX() - r, p.getY() - r, 2 * r, 2 * r)); 478 481 479 482 g2d.setColor(old); 480 483 } 481 484 482 485 static void dot(Graphics2D g2d, Point2D p, double r) { 483 486 dot(g2d, p, r, Color.RED); 484 487 } 485 488 486 489 void refresh() { 487 490 setState(new State.Invalid(state)); -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/LaneGui.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 29 30 private final Point2D center = new Point2D.Double(); 30 31 private final Ellipse2D circle = new Ellipse2D.Double(); 31 32 32 33 private Point2D dragDelta; 33 34 34 35 private LengthSlider() {} 35 36 36 37 @Override 37 38 public void paint(Graphics2D g2d, State state) { … … 39 40 g2d.setColor(Color.BLUE); 40 41 g2d.fill(circle); 41 42 42 43 final String len = METER_FORMAT.format(getLength() * getRoad().getContainer().getMpp()); 43 44 final Rectangle2D bounds = circle.getBounds2D(); … … 46 47 } 47 48 } 48 49 49 50 private boolean isVisible(State state) { 50 51 if (state instanceof State.OutgoingActive) { 51 52 return LaneGui.this.equals(((State.OutgoingActive) state).getLane()); 52 53 } 53 54 54 55 return false; 55 56 } 56 57 57 58 @Override 58 59 public boolean contains(Point2D p, State state) { 59 60 return isVisible(state) && circle.contains(p); 60 61 } 61 62 62 63 @Override 63 64 public Type getType() { 64 65 return Type.INCOMING_CONNECTOR; 65 66 } 66 67 67 68 @Override 68 69 boolean beginDrag(double x, double y) { … … 70 71 return true; 71 72 } 72 73 73 74 @Override 74 75 State drag(double x, double y, InteractiveElement target, State old) { … … 76 77 return new State.Dirty(old); 77 78 } 78 79 79 80 @Override 80 81 State drop(double x, double y, InteractiveElement target, State old) { … … 82 83 return old; 83 84 } 84 85 85 86 void move(double x, double y, boolean updateModel) { 86 87 final double r = getRoad().connectorRadius; 87 88 88 89 final double offset = getRoad().getOffset(x, y); 89 90 final double newLength = getModel().getOutgoingRoadEnd().isFromEnd() ? offset : getRoad().getLength() 90 91 - offset; 91 92 final double adjustedLength = min(max(newLength, 0.1), getRoad().getLength()); 92 93 93 94 length = adjustedLength; 94 95 if (updateModel) { 95 96 getModel().setLength(adjustedLength * getRoad().getContainer().getMpp()); 96 97 } 97 98 98 99 center.setLocation(x, y); 99 100 circle.setFrame(x - r, y - r, 2 * r, 2 * r); 100 101 } 101 102 102 103 public void move(Point2D loc) { 103 104 final double x = loc.getX(); 104 105 final double y = loc.getY(); 105 106 final double r = getRoad().connectorRadius; 106 107 107 108 center.setLocation(x, y); 108 109 circle.setFrame(x - r, y - r, 2 * r, 2 * r); 109 110 } 110 111 111 112 @Override 112 113 int getZIndex() { … … 114 115 } 115 116 } 116 117 117 118 final class OutgoingConnector extends InteractiveElement { 118 119 private final Point2D center = new Point2D.Double(); 119 120 private final Ellipse2D circle = new Ellipse2D.Double(); 120 121 121 122 private Point2D dragLocation; 122 123 private IncomingConnector dropTarget; 123 124 124 125 private OutgoingConnector() {} 125 126 126 127 @Override 127 128 public void paintBackground(Graphics2D g2d, State state) { … … 129 130 final Composite old = g2d.getComposite(); 130 131 g2d.setComposite(((AlphaComposite) old).derive(0.2f)); 131 132 132 133 g2d.setColor(new Color(255, 127, 31)); 133 134 LaneGui.this.fill(g2d); 134 135 135 136 g2d.setComposite(old); 136 137 } 137 138 138 139 if (dragLocation != null) { 139 140 final State.Connecting s = (State.Connecting) state; 140 141 final Path2D path = new Path2D.Double(); 141 142 path.moveTo(center.getX(), center.getY()); 142 143 143 144 final List<RoadGui.ViaConnector> vias = s.getViaConnectors(); 144 145 for (int i = 0; i < vias.size() - 1; i += 2) { … … 151 152 path.lineTo(last.getCenter().getX(), last.getCenter().getY()); 152 153 } 153 154 154 155 if (dropTarget == null) { 155 156 g2d.setColor(GuiContainer.RED); … … 159 160 path.lineTo(dropTarget.getCenter().getX(), dropTarget.getCenter().getY()); 160 161 } 161 162 162 163 g2d.setStroke(getContainer().getConnectionStroke()); 163 164 g2d.draw(path); 164 165 } 165 166 } 166 167 167 168 @Override 168 169 public void paint(Graphics2D g2d, State state) { … … 172 173 g2d.setComposite(((AlphaComposite) old).derive(1f)); 173 174 } 174 175 175 176 g2d.setColor(Color.WHITE); 176 177 g2d.fill(circle); … … 178 179 } 179 180 } 180 181 181 182 private boolean isActive(State state) { 182 183 return state instanceof State.OutgoingActive 183 184 && LaneGui.this.equals(((State.OutgoingActive) state).getLane()); 184 185 } 185 186 186 187 private boolean isVisible(State state) { 187 188 if (state instanceof State.Connecting) { 188 189 return ((State.Connecting) state).getLane().equals(getModel()); 189 190 } 190 191 191 192 return !getRoad().getModel().isPrimary() && getModel().getOutgoingJunction().isPrimary(); 192 193 } 193 194 194 195 @Override 195 196 public boolean contains(Point2D p, State state) { 196 197 return isVisible(state) && (circle.contains(p) || LaneGui.this.contains(p)); 197 198 } 198 199 199 200 @Override 200 201 public Type getType() { 201 202 return Type.OUTGOING_CONNECTOR; 202 203 } 203 204 204 205 @Override 205 206 public State activate(State old) { 206 207 return new State.OutgoingActive(LaneGui.this); 207 208 } 208 209 209 210 @Override 210 211 boolean beginDrag(double x, double y) { 211 212 return circle.contains(x, y); 212 213 } 213 214 214 215 @Override 215 216 State.Connecting drag(double x, double y, InteractiveElement target, State old) { 216 217 dragLocation = new Point2D.Double(x, y); 217 218 dropTarget = null; 218 219 219 220 if (!(old instanceof State.Connecting)) { 220 221 return new State.Connecting(getModel()); 221 222 } 222 223 223 224 final State.Connecting s = (State.Connecting) old; 224 225 if (target != null && target.getType() == Type.INCOMING_CONNECTOR) { 225 226 dropTarget = (IncomingConnector) target; 226 227 227 228 return (s.getViaConnectors().size() & 1) == 0 ? s : s.pop(); 228 229 } else if (target != null && target.getType() == Type.VIA_CONNECTOR) { 229 230 return s.next((RoadGui.ViaConnector) target); 230 231 } 231 232 232 233 return s; 233 234 } 234 235 235 236 @Override 236 237 State drop(double x, double y, InteractiveElement target, State old) { … … 240 241 return activate(old); 241 242 } 242 243 243 244 final List<Road> via = new ArrayList<>(); 244 245 assert (s.getViaConnectors().size() & 1) == 0; … … 249 250 via.add(a.getRoadEnd().getRoad()); 250 251 } 251 252 252 253 getModel().addTurn(via, dropTarget.getRoadEnd()); 253 254 dropTarget = null; 254 255 return new State.Dirty(activate(old)); 255 256 } 256 257 257 258 public Point2D getCenter() { 258 259 return (Point2D) center.clone(); 259 260 } 260 261 261 262 void move(double x, double y) { 262 263 final double r = getRoad().connectorRadius; 263 264 264 265 center.setLocation(x, y); 265 266 circle.setFrame(x - r, y - r, 2 * r, 2 * r); 266 267 } 267 268 268 269 @Override 269 270 int getZIndex() { … … 271 272 } 272 273 } 273 274 274 275 static final NumberFormat METER_FORMAT = new DecimalFormat("0.0m"); 275 276 276 277 private final RoadGui road; 277 278 private final Lane lane; 278 279 279 280 final Path2D area = new Path2D.Double(); 280 281 281 282 final OutgoingConnector outgoing = new OutgoingConnector(); 282 283 final LengthSlider lengthSlider; 283 284 284 285 private Shape clip; 285 286 private double length; 286 287 publicLaneGui(RoadGui road, Lane lane) {287 288 LaneGui(RoadGui road, Lane lane) { 288 289 this.road = road; 289 290 this.lane = lane; … … 291 292 this.length = lane.isExtra() ? lane.getLength() / road.getContainer().getMpp() : Double.NaN; 292 293 } 293 294 294 295 public double getLength() { 295 296 return lane.isExtra() ? length : road.getLength(); 296 297 } 297 298 298 299 public Lane getModel() { 299 300 return lane; 300 301 } 301 302 302 303 public RoadGui getRoad() { 303 304 return road; 304 305 } 305 306 306 307 public GuiContainer getContainer() { 307 308 return getRoad().getContainer(); 308 309 } 309 310 310 311 public Path recalculate(Path inner, Path2D innerLine) { 311 312 area.reset(); 312 313 313 314 final double W = getContainer().getLaneWidth(); 314 315 final double L = getLength(); 315 316 316 317 final double WW = 3 / getContainer().getMpp(); 317 318 318 319 final LaneGui left = left(); 319 320 final Lane leftModel = left == null ? null : left.getModel(); … … 321 322 || !leftModel.getOutgoingRoadEnd().equals(getModel().getOutgoingRoadEnd()) ? Double.NEGATIVE_INFINITY 322 323 : leftModel.getKind() == Lane.Kind.EXTRA_LEFT ? left.getLength() : L; 323 324 324 325 final Path outer; 325 326 if (getModel().getKind() == Lane.Kind.EXTRA_LEFT) { 326 327 final double AL = 30 / getContainer().getMpp(); 327 328 final double SL = max(L, leftLength + AL); 328 329 329 330 outer = inner.offset(W, SL, SL + AL, 0); 330 331 area(area, inner.subpath(0, L, true), outer.subpath(0, L + WW, true)); 331 332 332 333 lengthSlider.move(inner.getPoint(L, true)); 333 334 334 335 if (L > leftLength) { 335 336 innerLine.append(inner.subpath(leftLength + WW, L, true).getIterator(), leftLength >= 0 … … 341 342 outer = inner.offset(W, L, L + WW, 0); 342 343 area(area, inner.subpath(0, L + WW, true), outer.subpath(0, L, true)); 343 344 344 345 lengthSlider.move(outer.getPoint(L, true)); 345 346 } else { 346 347 outer = inner.offset(W, -1, -1, W); 347 348 area(area, inner, outer); 348 349 349 350 if (leftLength < L) { 350 351 innerLine.append(inner.subpath(leftLength + WW, L, true).getIterator(), leftLength >= 0 … … 352 353 } 353 354 } 354 355 355 356 return outer; 356 357 } 357 358 358 359 private LaneGui left() { 359 360 final List<LaneGui> lanes = getRoad().getLanes(getModel().getOutgoingRoadEnd()); … … 361 362 return i > 0 ? lanes.get(i - 1) : null; 362 363 } 363 364 364 365 public void fill(Graphics2D g2d) { 365 366 final Shape old = g2d.getClip(); … … 368 369 g2d.setClip(old); 369 370 } 370 371 371 372 public void setClip(Shape clip) { 372 373 this.clip = clip; 373 374 } 374 375 375 376 public boolean contains(Point2D p) { 376 377 return area.contains(p) && clip.contains(p); -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/Path.java
r26316 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 22 23 * A path that allows constructing offset curves/parallel curves with a somewhat crude straight 23 24 * skeleton implementation. 24 * 25 * 25 26 * @author Ben Schulz 26 27 */ … … 28 29 private static final class SimplePathIterator implements PathIterator { 29 30 private final SimplePathIterator previous; 30 31 31 32 private final int type; 32 33 private final double[] coords; 33 34 34 35 private boolean done = false; 35 36 publicSimplePathIterator(SimplePathIterator previous, int type, double... coords) {36 37 SimplePathIterator(SimplePathIterator previous, int type, double... coords) { 37 38 this.previous = previous; 38 39 this.type = type; 39 40 this.coords = coords; 40 41 } 41 42 publicSimplePathIterator(int type, double... coords) {42 43 SimplePathIterator(int type, double... coords) { 43 44 this(null, type, coords); 44 45 } 45 46 46 47 @Override 47 48 public int getWindingRule() { 48 49 return WIND_NON_ZERO; 49 50 } 50 51 51 52 @Override 52 53 public boolean isDone() { 53 54 return done; 54 55 } 55 56 56 57 @Override 57 58 public void next() { … … 62 63 } 63 64 } 64 65 65 66 @Override 66 67 public int currentSegment(float[] coords) { … … 70 71 throw new NoSuchElementException("Iterator is already done."); 71 72 } 72 73 73 74 for (int i = 0; i < 6; ++i) { 74 75 coords[i] = (float) this.coords[i]; 75 76 } 76 77 77 78 return type; 78 79 } 79 80 80 81 @Override 81 82 public int currentSegment(double[] coords) { … … 85 86 throw new NoSuchElementException("Iterator is already done."); 86 87 } 87 88 88 89 for (int i = 0; i < 6; ++i) { 89 90 coords[i] = this.coords[i]; 90 91 } 91 92 92 93 return type; 93 94 } 94 95 } 96 95 96 } 97 97 98 private static final class Line extends Path { 98 99 private final Path previous; 99 100 100 101 private final double endX; 101 102 private final double endY; 102 103 103 104 private final double angle; 104 105 105 106 private final double length; 106 107 publicLine(Path previous, double x, double y, double length) {107 108 Line(Path previous, double x, double y, double length) { 108 109 this.previous = previous; 109 110 110 111 this.endX = x; 111 112 this.endY = y; 112 113 113 114 this.angle = angle(previous.getEnd(), getEnd()); 114 115 115 116 this.length = length; 116 117 } 117 118 118 119 @Override 119 120 public Point2D getStart() { 120 121 return previous.getStart(); 121 122 } 122 123 123 124 @Override 124 125 public Point2D getEnd() { 125 126 return new Point2D.Double(endX, endY); 126 127 } 127 128 128 129 @Override 129 130 public double getEndAngle() { 130 131 return angle; 131 132 } 132 133 133 134 @Override 134 135 public double getLength() { 135 136 return previous.getLength() + length; 136 137 } 137 138 138 139 @Override 139 140 public Path offset(double ws, double m1, double m2, double we) { 140 141 return offsetInternal(ws, m1, m2, we, angle); 141 142 } 142 143 143 144 @Override 144 145 Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) { 145 146 final double PL = previous.getLength(); 146 147 final double ML = PL + length; 147 148 148 149 final Path prev = previous.offsetInternal(ws, m1, m2, we, angle); 149 150 150 151 final double wStart = PL <= m1 ? ws : m2 <= PL ? we : ws + (PL - m1) * (we - ws) / (m2 - m1); 151 152 final Point2D from = prev.getEnd(); 152 153 final Point2D to = offsetEnd(wStart, endAngle); 153 154 154 155 if (abs(minAngleDiff(angle, angle(from, to))) > PI / 100) { 155 156 return previous.offsetInternal(ws, m1, m2, we, endAngle); 156 157 } 157 158 158 159 if (ML <= m1) { 159 160 return simpleOffset(prev, ws, endAngle); … … 161 162 return simpleOffset(prev, we, endAngle); 162 163 } 163 164 164 165 final double LL = from.distance(to); 165 166 166 167 final Point2D m1o = PL <= m1 ? relativePoint(prev.getEnd(), LL * (m1 - PL) / length, angle) : null; 167 168 final Point2D m2t = m2 <= ML ? relativePoint(getEnd(), LL * (ML - m2) / length, angle + PI) : null; 168 169 final Point2D m2o = m2t == null ? null : relativePoint(m2t, we, (angle + endAngle - PI) / 2); 169 170 170 171 if (m1o != null && m2o != null) { 171 172 final Line l1 = new Line(prev, m1o.getX(), m1o.getY(), m1 - PL); 172 173 final Line l2 = new Line(l1, m2o.getX(), m2o.getY(), m2 - m1); 173 174 174 175 final Point2D end = offsetEnd(we, endAngle); 175 176 176 177 return new Line(l2, end.getX(), end.getY(), ML - m2); 177 178 } else if (m1o != null) { 178 179 final Line l1 = new Line(prev, m1o.getX(), m1o.getY(), m1 - PL); 179 180 180 181 final double w = ws + (ML - m1) * (we - ws) / (m2 - m1); 181 182 final Point2D end = offsetEnd(w, endAngle); 182 183 183 184 return new Line(l1, end.getX(), end.getY(), ML - m1); 184 185 } else if (m2o != null) { 185 186 final Line l2 = new Line(prev, m2o.getX(), m2o.getY(), m2 - PL); 186 187 187 188 final Point2D end = offsetEnd(we, endAngle); 188 189 189 190 return new Line(l2, end.getX(), end.getY(), ML - m2); 190 191 } else { … … 194 195 } 195 196 } 196 197 197 198 private Path simpleOffset(Path prev, double w, double endAngle) { 198 199 final Point2D offset = offsetEnd(w, endAngle); 199 200 return new Line(prev, offset.getX(), offset.getY(), length); 200 201 } 201 202 202 203 private Point2D offsetEnd(double w, double endAngle) { 203 204 final double da2 = minAngleDiff(angle, endAngle) / 2; 204 205 final double hypotenuse = w / cos(da2); 205 206 206 207 return relativePoint(getEnd(), hypotenuse, angle + PI / 2 + da2); 207 208 } 208 209 209 210 @Override 210 211 public SimplePathIterator getIterator() { … … 212 213 0, 0, 0); 213 214 } 214 215 215 216 @Override 216 217 public Path subpath(double from, double to) { 217 218 final double PL = previous.getLength(); 218 219 final double ML = PL + length; 219 220 220 221 if (from > ML) { 221 222 throw new IllegalArgumentException("from > length"); … … 223 224 throw new IllegalArgumentException("to > length"); 224 225 } 225 226 226 227 if (to < PL) { 227 228 return previous.subpath(from, to); 228 229 } 229 230 230 231 final Point2D end = to < ML ? getPoint(to) : new Point2D.Double(endX, endY); 231 232 232 233 final double EL = min(ML, to); 233 234 if (PL <= from) { … … 238 239 } 239 240 } 240 241 241 242 @Override 242 243 public Point2D getPoint(double offset) { 243 244 final double PL = previous.getLength(); 244 245 final double ML = PL + length; 245 246 246 247 if (offset > ML) { 247 248 throw new IllegalArgumentException("offset > length"); 248 249 } 249 250 250 251 if (offset <= ML && offset >= PL) { 251 252 final double LL = previous.getEnd().distance(getEnd()); … … 255 256 } 256 257 } 257 258 258 259 @Override 259 260 SimplePathIterator getIteratorInternal(double endAngle) { … … 261 262 } 262 263 } 263 264 264 265 // TODO curves are still somewhat broken 265 266 private static class Curve extends Path { 266 267 private final Path previous; 267 268 268 269 private final double height; 269 270 270 271 private final double centerX; 271 272 private final double centerY; 272 273 private final double centerToFromAngle; 273 274 274 275 private final double endX; 275 276 private final double endY; 276 277 277 278 private final double fromAngle; 278 279 private final double fromRadius; 279 280 private final double toRadius; 280 281 private final double angle; 281 282 282 283 private final double length; 283 284 284 285 private Curve(Path previous, double r1, double r2, double a, double length, double fromAngle) { 285 286 this.previous = previous; … … 289 290 this.angle = a; 290 291 this.length = length; 291 292 292 293 final Point2D from = previous.getEnd(); 293 294 this.centerToFromAngle = fromAngle - signum(a) * PI / 2; 294 295 final Point2D center = relativePoint(from, r1, centerToFromAngle + PI); 295 296 296 297 final double toAngle = centerToFromAngle + a; 297 298 this.endX = center.getX() + r2 * cos(toAngle); 298 299 this.endY = center.getY() - r2 * sin(toAngle); 299 300 300 301 this.centerX = center.getX(); 301 302 this.centerY = center.getY(); 302 303 303 304 final double y = new Line2D.Double(center, from).ptLineDist(endX, endY); 304 305 this.height = y / sin(angle); 305 306 } 306 307 publicCurve(Path previous, double r1, double r2, double a, double length) {307 308 Curve(Path previous, double r1, double r2, double a, double length) { 308 309 this(previous, r1, r2, a, length, previous.getEndAngle()); 309 310 } 310 311 312 @Override 311 313 public Point2D getStart() { 312 314 return previous.getStart(); 313 315 } 314 316 315 317 @Override 316 318 public Point2D getEnd() { 317 319 return new Point2D.Double(endX, endY); 318 320 } 319 321 320 322 @Override 321 323 public double getEndAngle() { 322 324 return fromAngle + angle; 323 325 } 324 326 325 327 @Override 326 328 public double getLength() { 327 329 return previous.getLength() + length; 328 330 } 329 331 330 332 @Override 331 333 public Path offset(double ws, double m1, double m2, double we) { 332 334 return offsetInternal(ws, m1, m2, we, previous.getEndAngle() + angle); 333 335 } 334 336 335 337 @Override 336 338 Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) { 337 339 final double PL = previous.getLength(); 338 340 final double ML = PL + length; 339 341 340 342 final Path prev = previous.offsetInternal(ws, m1, m2, we, fromAngle); 341 343 342 344 if (ML <= m1) { 343 345 return simpleOffset(prev, ws); … … 345 347 return simpleOffset(prev, we); 346 348 } 347 349 348 350 final double s = signum(angle); 349 351 350 352 if (PL < m1 && m2 < ML) { 351 353 final double l1 = m1 - PL; 352 354 final double a1 = angle(l1); 353 355 final double r1 = radius(a1) - s * ws; 354 356 355 357 final Curve c1 = new Curve(prev, fromRadius - ws, r1, offsetAngle(prev, a1), l1, fromAngle); 356 358 357 359 final double l2 = m2 - m1; 358 360 final double a2 = angle(l2); 359 361 final double r2 = radius(a2) - s * we; 360 362 361 363 final Curve c2 = new Curve(c1, r1, r2, a2 - a1, l2); 362 364 363 365 return new Curve(c2, r2, toRadius - s * we, angle - a2, ML - m2); 364 366 } else if (PL < m1) { … … 366 368 final double a1 = angle(l1); 367 369 final double r1 = radius(a1) - s * ws; 368 370 369 371 final Curve c1 = new Curve(prev, fromRadius - s * ws, r1, offsetAngle(prev, a1), l1, fromAngle); 370 372 371 373 final double w = ws + (ML - m1) * (we - ws) / (m2 - m1); 372 374 373 375 return new Curve(c1, r1, toRadius - s * w, angle - a1, ML - m1); 374 376 } else if (m2 < ML) { 375 377 final double w = ws + (PL - m1) * (we - ws) / (m2 - m1); 376 378 377 379 final double l2 = m2 - PL; 378 380 final double a2 = angle(l2); 379 381 final double r2 = radius(a2) - s * we; 380 382 381 383 final Curve c2 = new Curve(prev, fromRadius - s * w, r2, offsetAngle(prev, a2), l2, fromAngle); 382 384 383 385 return new Curve(c2, r2, toRadius - s * we, angle - a2, ML - m2); 384 386 } else { 385 387 final double w1 = ws + (PL - m1) * (we - ws) / (m2 - m1); 386 388 final double w2 = we - (m2 - ML) * (we - ws) / (m2 - m1); 387 389 388 390 return new Curve(prev, fromRadius - s * w1, toRadius - s * w2, offsetAngle(prev, angle), length, 389 391 fromAngle); 390 392 } 391 393 } 392 394 393 395 private double angle(double l) { 394 396 return l * angle / length; 395 397 } 396 398 397 399 private double radius(double a) { 398 400 return hypot(fromRadius * cos(a), height * sin(a)); 399 401 } 400 402 401 403 private double offsetAngle(Path prev, double a) { 402 return a; // + GuiUtil.normalize(previous.getEndAngle()404 return a; // + GuiUtil.normalize(previous.getEndAngle() 403 405 // - prev.getEndAngle()); 404 406 } 405 407 406 408 private Path simpleOffset(Path prev, double w) { 407 409 final double s = signum(angle); 408 410 return new Curve(prev, fromRadius - s * w, toRadius - s * w, offsetAngle(prev, angle), length, fromAngle); 409 411 } 410 412 411 413 @Override 412 414 public SimplePathIterator getIterator() { 413 415 return getIteratorInternal(previous.getEndAngle() + angle); 414 416 } 415 417 416 418 @Override 417 419 public Path subpath(double from, double to) { 418 420 final double PL = previous.getLength(); 419 421 final double ML = PL + length; 420 422 421 423 if (from > ML) { 422 424 throw new IllegalArgumentException("from > length"); … … 424 426 throw new IllegalArgumentException("to > length"); 425 427 } 426 428 427 429 if (to < PL) { 428 430 return previous.subpath(from, to); 429 431 } 430 432 431 433 final double toA = to < ML ? angle(to - PL) : angle; 432 434 final double toR = to < ML ? radius(toA) : toRadius; 433 435 434 436 final double fromA = from > PL ? angle(from - PL) : 0; 435 437 final double fromR = from > PL ? radius(fromA) : fromRadius; 436 438 437 439 final double a = toA - fromA; 438 440 final double l = min(ML, to) - max(PL, from); 439 441 440 442 if (from >= PL) { 441 443 final Point2D start = getPoint(from); 442 444 final double fa = fromAngle + fromA; 443 445 444 446 return new Curve(new Start(start.getX(), start.getY(), fa), fromR, toR, a, l, fa); 445 447 } else { … … 447 449 } 448 450 } 449 451 450 452 @Override 451 453 public Point2D getPoint(double offset) { 452 454 final double PL = previous.getLength(); 453 455 final double ML = PL + length; 454 456 455 457 if (offset <= ML && offset >= PL) { 456 458 final double a = abs(angle(offset - PL)); 457 459 final double w = fromRadius * cos(a); 458 460 final double h = -height * sin(a); 459 461 460 462 final double r = centerToFromAngle; // rotation angle 461 463 final double x = w * cos(r) + h * sin(r); 462 464 final double y = -w * sin(r) + h * cos(r); 463 465 464 466 return new Point2D.Double(centerX + x, centerY + y); 465 467 } else { … … 467 469 } 468 470 } 469 471 470 472 @Override 471 473 SimplePathIterator getIteratorInternal(double endAngle) { 472 474 final Point2D cp1 = relativePoint(previous.getEnd(), cpf(angle, fromRadius), previous.getEndAngle()); 473 475 final Point2D cp2 = relativePoint(getEnd(), cpf(angle, toRadius), endAngle + PI); 474 476 475 477 return new SimplePathIterator(previous.getIteratorInternal(getEndAngle()), PathIterator.SEG_CUBICTO, // 476 478 cp1.getX(), cp1.getY(), cp2.getX(), cp2.getY(), endX, endY // 477 479 ); 478 479 } 480 } 481 480 481 } 482 } 483 482 484 private static class Start extends Path { 483 485 private final double x; 484 486 private final double y; 485 487 486 488 private final double endAngle; 487 488 publicStart(double x, double y, double endAngle) {489 490 Start(double x, double y, double endAngle) { 489 491 this.x = x; 490 492 this.y = y; 491 493 this.endAngle = endAngle; 492 494 } 493 494 publicStart(double x, double y) {495 496 Start(double x, double y) { 495 497 this(x, y, Double.NaN); 496 498 } 497 499 500 @Override 498 501 public Point2D getStart() { 499 502 return new Point2D.Double(x, y); 500 503 } 501 504 505 @Override 502 506 public Point2D getEnd() { 503 507 return new Point2D.Double(x, y); 504 508 } 505 509 506 510 @Override 507 511 public double getEndAngle() { … … 509 513 throw new UnsupportedOperationException(); 510 514 } 511 515 512 516 return endAngle; 513 517 } 514 518 515 519 @Override 516 520 public double getLength() { 517 521 return 0; 518 522 } 519 523 520 524 @Override 521 525 public Path offset(double ws, double m1, double m2, double we) { 522 526 throw new UnsupportedOperationException(); 523 527 } 524 528 525 529 @Override 526 530 Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) { … … 528 532 return new Start(offset.getX(), offset.getY(), endAngle); 529 533 } 530 534 531 535 @Override 532 536 public SimplePathIterator getIterator() { 533 537 return new SimplePathIterator(PathIterator.SEG_MOVETO, x, y, 0, 0, 0, 0); 534 538 } 535 539 536 540 @Override 537 541 public Path subpath(double from, double to) { … … 542 546 throw new IllegalArgumentException("from < 0"); 543 547 } 544 548 545 549 return this; 546 550 } 547 551 548 552 @Override 549 553 public Point2D getPoint(double offset) { … … 554 558 } 555 559 } 556 560 557 561 @Override 558 562 SimplePathIterator getIteratorInternal(double endAngle) { … … 560 564 } 561 565 } 562 566 563 567 public static Path create(double x, double y) { 564 568 return new Start(x, y); 565 569 } 566 570 567 571 public Path lineTo(double x, double y, double length) { 568 572 return new Line(this, x, y, length); 569 573 } 570 574 571 575 public Path curveTo(double r1, double r2, double a, double length) { 572 576 return new Curve(this, r1, r2, a, length); 573 577 } 574 578 575 579 public abstract Path offset(double ws, double m1, double m2, double we); 576 580 577 581 abstract Path offsetInternal(double ws, double m1, double m2, double we, double endAngle); 578 582 579 583 public abstract double getLength(); 580 584 581 585 public abstract double getEndAngle(); 582 586 583 587 public abstract Point2D getStart(); 584 588 585 589 public abstract Point2D getEnd(); 586 590 587 591 public abstract SimplePathIterator getIterator(); 588 592 589 593 abstract SimplePathIterator getIteratorInternal(double endAngle); 590 594 591 595 public abstract Path subpath(double from, double to); 592 596 593 597 public Path subpath(double from, double to, boolean fixArgs) { 594 598 if (fixArgs) { … … 596 600 to = min(max(to, 0), getLength()); 597 601 } 598 602 599 603 return subpath(from, to); 600 604 } 601 605 602 606 public abstract Point2D getPoint(double offset); 603 607 604 608 public Point2D getPoint(double offset, boolean fixArgs) { 605 609 if (fixArgs) { 606 610 offset = min(max(offset, 0), getLength()); 607 611 } 608 612 609 613 return getPoint(offset); 610 614 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ReversePathIterator.java
r26154 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 6 7 /** 7 8 * Path iterator which reverses the path of a given iterator. 8 * 9 * 9 10 * <p> 10 11 * The given (unreversed) iterator must start with a {@code PathIterator.SEG_MOVETO} and must not … … 12 13 * returned by {@code Path.getIterator} which has exactly those properties. 13 14 * </p> 14 * 15 * 15 16 * @author Ben Schulz 16 17 */ 17 class ReversePathIterator implements PathIterator {18 final class ReversePathIterator implements PathIterator { 18 19 private static final int[] COUNTS = { 19 20 2, // SEG_MOVETO = 0 … … 23 24 0, // SEG_CLOSE = 4 24 25 }; 25 26 26 27 public static ReversePathIterator reverse(PathIterator it) { 27 28 return new ReversePathIterator(it); 28 29 } 29 30 30 31 private static int[] reverseTypes(int[] types, int length) { 31 32 if (length > 0 && types[0] != SEG_MOVETO) { … … 33 34 throw new IllegalArgumentException("Can not reverse path without initial SEG_MOVETO."); 34 35 } 35 36 36 37 final int[] result = new int[length]; 37 38 38 39 result[0] = SEG_MOVETO; 39 40 40 41 int lower = 1; 41 42 int upper = length - 1; 42 43 43 44 while (lower <= upper) { 44 45 result[lower] = types[upper]; 45 46 result[upper] = types[lower]; 46 47 47 48 ++lower; 48 49 --upper; 49 50 } 50 51 51 52 return result; 52 53 } 53 54 54 55 private static double[] reverseCoords(double[] coords, int length) { 55 56 final double[] result = new double[length]; 56 57 57 58 int lower = 0; 58 59 int upper = length - 2; 59 60 60 61 while (lower <= upper) { 61 62 result[lower] = coords[upper]; … … 63 64 result[upper] = coords[lower]; 64 65 result[upper + 1] = coords[lower + 1]; 65 66 66 67 lower += 2; 67 68 upper -= 2; 68 69 } 69 70 70 71 return result; 71 72 } 72 73 73 74 private final int winding; 74 75 75 76 private final int[] types; 76 77 private int typesIndex = 0; 77 78 78 79 private final double[] coords; 79 80 private int coordsIndex = 0; 80 81 81 82 private ReversePathIterator(PathIterator it) { 82 83 this.winding = it.getWindingRule(); 83 84 84 85 double[] tmpCoords = new double[62]; 85 86 int[] tmpTypes = new int[11]; 86 87 87 88 int tmpCoordsI = 0; 88 89 int tmpTypesI = 0; 89 90 90 91 while (!it.isDone()) { 91 92 if (tmpTypesI >= tmpTypes.length) { 92 93 tmpTypes = Arrays.copyOf(tmpTypes, 2 * tmpTypes.length); 93 94 } 94 95 95 96 final double[] cs = new double[6]; 96 97 final int t = it.currentSegment(cs); 97 98 tmpTypes[tmpTypesI++] = t; 98 99 final int count = COUNTS[t]; 99 100 100 101 if (tmpCoordsI + count > tmpCoords.length) { 101 102 tmpCoords = Arrays.copyOf(tmpCoords, 2 * tmpCoords.length); … … 103 104 System.arraycopy(cs, 0, tmpCoords, tmpCoordsI, count); 104 105 tmpCoordsI += count; 105 106 106 107 it.next(); 107 108 } 108 109 109 110 this.types = reverseTypes(tmpTypes, tmpTypesI); 110 111 this.coords = reverseCoords(tmpCoords, tmpCoordsI); 111 112 } 112 113 113 114 @Override 114 115 public int getWindingRule() { 115 116 return winding; 116 117 } 117 118 118 119 @Override 119 120 public boolean isDone() { 120 121 return typesIndex >= types.length; 121 122 } 122 123 123 124 @Override 124 125 public void next() { … … 126 127 ++typesIndex; 127 128 } 128 129 129 130 @Override 130 131 public int currentSegment(float[] coords) { 131 132 final double[] tmp = new double[6]; 132 133 final int type = currentSegment(tmp); 133 134 134 135 coords[0] = (float) tmp[0]; 135 136 coords[1] = (float) tmp[1]; … … 138 139 coords[4] = (float) tmp[4]; 139 140 coords[5] = (float) tmp[5]; 140 141 141 142 return type; 142 143 } 143 144 144 145 @Override 145 146 public int currentSegment(double[] coords) { -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/RoadGui.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 43 44 final class ViaConnector extends InteractiveElement { 44 45 private final Road.End end; 45 46 46 47 private final Line2D line; 47 48 private final float strokeWidth; 48 49 publicViaConnector(Road.End end) {49 50 ViaConnector(Road.End end) { 50 51 this.end = end; 51 52 this.line = new Line2D.Double(getLeftCorner(end), getRightCorner(end)); 52 53 this.strokeWidth = (float) (3 * getContainer().getLaneWidth() / 4); 53 54 } 54 55 55 56 @Override 56 57 void paint(Graphics2D g2d, State state) { … … 61 62 } 62 63 } 63 64 64 65 @Override 65 66 boolean contains(Point2D p, State state) { … … 67 68 return false; 68 69 } 69 70 70 71 final Point2D closest = closest(line, p); 71 72 return p.distance(closest) <= strokeWidth / 2; 72 73 } 73 74 74 75 private boolean isVisible(State state) { 75 76 if (!(state instanceof State.Connecting)) { 76 77 return false; 77 78 } 78 79 79 80 final State.Connecting s = (State.Connecting) state; 80 81 81 82 if (s.getJunction().equals(end.getJunction()) || equals(s.getBacktrackViaConnector())) { 82 83 return true; … … 85 86 return true; 86 87 } 87 88 88 89 return false; 89 90 } 90 91 91 92 private Road getRoadModel() { 92 93 return getModel(); 93 94 } 94 95 95 96 public RoadGui getRoad() { 96 97 return RoadGui.this; 97 98 } 98 99 99 100 @Override 100 101 Type getType() { 101 102 return Type.VIA_CONNECTOR; 102 103 } 103 104 104 105 @Override 105 106 int getZIndex() { 106 107 return 1; 107 108 } 108 109 109 110 public Road.End getRoadEnd() { 110 111 return end; 111 112 } 112 113 113 114 public Point2D getCenter() { 114 115 return relativePoint(line.getP1(), line.getP1().distance(line.getP2()) / 2, angle(line.getP1(), line.getP2())); 115 116 } 116 117 } 117 118 118 119 private final class Extender extends InteractiveElement { 119 120 private final Road.End end; 120 121 private final Way way; 121 122 122 123 private final Line2D line; 123 124 publicExtender(Road.End end, Way way, double angle) {124 125 Extender(Road.End end, Way way, double angle) { 125 126 this.end = end; 126 127 this.way = way; 127 128 this.line = new Line2D.Double(a.getPoint(), relativePoint(a.getPoint(), getContainer().getLaneWidth() * 4, angle)); 128 129 } 129 130 130 131 @Override 131 132 void paint(Graphics2D g2d, State state) { … … 134 135 g2d.draw(line); 135 136 } 136 137 137 138 @Override 138 139 boolean contains(Point2D p, State state) { 139 140 final BasicStroke stroke = (BasicStroke) getContainer().getConnectionStroke(); 140 141 final double strokeWidth = stroke.getLineWidth(); 141 142 142 143 final Point2D closest = closest(line, p); 143 144 return p.distance(closest) <= strokeWidth / 2; 144 145 } 145 146 146 147 @Override 147 148 State click(State old) { … … 149 150 return new State.Invalid(old); 150 151 } 151 152 152 153 @Override 153 154 Type getType() { 154 155 return Type.EXTENDER; 155 156 } 156 157 157 158 @Override 158 159 int getZIndex() { … … 160 161 } 161 162 } 162 163 163 164 private final class LaneAdder extends InteractiveElement { 164 165 private final Road.End end; 165 166 private final Lane.Kind kind; 166 167 167 168 private final Point2D center; 168 169 private final Ellipse2D background; 169 170 publicLaneAdder(Road.End end, Lane.Kind kind) {170 171 LaneAdder(Road.End end, Lane.Kind kind) { 171 172 this.end = end; 172 173 this.kind = kind; 173 174 174 175 final double a = getAngle(end) + PI; 175 176 final Point2D lc = getLeftCorner(end); 176 177 final Point2D rc = getRightCorner(end); 177 178 178 179 final double r = connectorRadius; 179 180 final double cx; … … 182 183 final JunctionGui j = getContainer().getGui(end.getJunction()); 183 184 final Point2D i = intersection(line(j.getPoint(), a), new Line2D.Double(lc, rc)); 184 185 185 186 cx = i.getX() + 21d / 16 * r * (2 * cos(a) + cos(a - PI / 2)); 186 187 cy = i.getY() - 21d / 16 * r * (2 * sin(a) + sin(a - PI / 2)); … … 189 190 cy = rc.getY() - 21d / 16 * r * (2 * sin(a) + sin(a + PI / 2)); 190 191 } 191 192 192 193 center = new Point2D.Double(cx, cy); 193 194 background = new Ellipse2D.Double(cx - r, cy - r, 2 * r, 2 * r); 194 195 } 195 196 196 197 @Override 197 198 void paint(Graphics2D g2d, State state) { … … 199 200 return; 200 201 } 201 202 202 203 g2d.setColor(Color.DARK_GRAY); 203 204 g2d.fill(background); 204 205 205 206 final double l = 2 * connectorRadius / 3; 206 207 final Line2D v = new Line2D.Double(center.getX(), center.getY() - l, center.getX(), center.getY() + l); 207 208 final Line2D h = new Line2D.Double(center.getX() - l, center.getY(), center.getX() + l, center.getY()); 208 209 209 210 g2d.setStroke(new BasicStroke((float) (connectorRadius / 5))); 210 211 g2d.setColor(Color.WHITE); … … 212 213 g2d.draw(h); 213 214 } 214 215 215 216 private boolean isVisible(State state) { 216 217 return end.getJunction().isPrimary(); 217 218 } 218 219 219 220 @Override 220 221 boolean contains(Point2D p, State state) { 221 222 return isVisible(state) && background.contains(p); 222 223 } 223 224 224 225 @Override 225 226 Type getType() { 226 227 return Type.LANE_ADDER; 227 228 } 228 229 229 230 @Override 230 231 int getZIndex() { 231 232 return 2; 232 233 } 233 234 234 235 @Override 235 236 public State click(State old) { … … 238 239 } 239 240 } 240 241 241 242 final class IncomingConnector extends InteractiveElement { 242 243 private final Road.End end; 243 244 private final List<LaneGui> lanes; 244 245 245 246 private final Point2D center = new Point2D.Double(); 246 247 private final Ellipse2D circle = new Ellipse2D.Double(); 247 248 248 249 private IncomingConnector(Road.End end) { 249 250 this.end = end; 250 251 251 252 final List<LaneGui> lanes = new ArrayList<>(end.getLanes().size()); 252 253 for (Lane l : end.getOppositeEnd().getLanes()) { … … 255 256 this.lanes = Collections.unmodifiableList(lanes); 256 257 } 257 258 258 259 @Override 259 260 public void paintBackground(Graphics2D g2d, State state) { … … 261 262 final Composite old = g2d.getComposite(); 262 263 g2d.setComposite(((AlphaComposite) old).derive(0.2f)); 263 264 264 265 g2d.setColor(new Color(255, 127, 31)); 265 266 266 267 for (LaneGui l : lanes) { 267 268 l.fill(g2d); 268 269 } 269 270 270 271 g2d.setComposite(old); 271 272 } 272 273 } 273 274 274 275 @Override 275 276 public void paint(Graphics2D g2d, State state) { … … 279 280 g2d.setComposite(((AlphaComposite) old).derive(1f)); 280 281 } 281 282 282 283 g2d.setColor(Color.LIGHT_GRAY); 283 284 g2d.fill(circle); 284 285 285 286 g2d.setComposite(old); 286 287 } 287 288 } 288 289 289 290 private boolean isActive(State state) { 290 291 if (!(state instanceof State.IncomingActive)) { 291 292 return false; 292 293 } 293 294 294 295 final Road.End roadEnd = ((State.IncomingActive) state).getRoadEnd(); 295 296 296 297 return roadEnd.equals(getRoadEnd()); 297 298 } 298 299 299 300 private boolean isVisible(State state) { 300 301 if (getModel().isPrimary() || !getRoadEnd().getJunction().isPrimary() … … 302 303 return false; 303 304 } 304 305 305 306 if (state instanceof State.Connecting) { 306 307 return ((State.Connecting) state).getJunction().equals(getRoadEnd().getJunction()); 307 308 } 308 309 309 310 return true; 310 311 } 311 312 312 313 @Override 313 314 public boolean contains(Point2D p, State state) { … … 317 318 return true; 318 319 } 319 320 320 321 for (LaneGui l : lanes) { 321 322 if (l.contains(p)) { … … 323 324 } 324 325 } 325 326 326 327 return false; 327 328 } 328 329 329 330 @Override 330 331 public Type getType() { 331 332 return Type.INCOMING_CONNECTOR; 332 333 } 333 334 334 335 @Override 335 336 public State activate(State old) { 336 337 return new State.IncomingActive(getRoadEnd()); 337 338 } 338 339 339 340 public Point2D getCenter() { 340 341 return (Point2D) center.clone(); 341 342 } 342 343 343 344 void move(double x, double y) { 344 345 final double r = connectorRadius; 345 346 346 347 center.setLocation(x, y); 347 348 circle.setFrame(x - r, y - r, 2 * r, 2 * r); 348 349 } 349 350 350 351 public Road.End getRoadEnd() { 351 352 return end; 352 353 } 353 354 354 355 public List<LaneGui> getLanes() { 355 356 return lanes; 356 357 } 357 358 358 359 @Override 359 360 int getZIndex() { 360 361 return 1; 361 362 } 362 363 363 364 public void add(LaneGui lane) { 364 365 lanes.add(lane); 365 366 } 366 367 } 367 368 368 369 // TODO rework to be a SegmentGui (with getModel()) 369 370 private final class Segment { 370 371 final Point2D to; 371 372 final Point2D from; 372 373 373 374 final Segment prev; 374 375 final Segment next; 375 376 376 377 final double length; 377 378 final double angle; 378 379 publicSegment(Segment next, List<Point2D> bends, JunctionGui a) {379 380 Segment(Segment next, List<Point2D> bends, JunctionGui a) { 380 381 final Point2D head = (Point2D) bends.get(0).clone(); 381 382 final List<Point2D> tail = bends.subList(1, bends.size()); 382 383 383 384 this.next = next; 384 385 this.to = head; … … 387 388 this.length = from.distance(to); 388 389 this.angle = angle(from, to); 389 390 390 391 // TODO create a factory method for the segments list and pass it to 391 392 // the constructor(s) 392 393 segments.add(this); 393 394 } 394 395 publicSegment(JunctionGui b, List<Point2D> bends, JunctionGui a) {395 396 Segment(JunctionGui b, List<Point2D> bends, JunctionGui a) { 396 397 this((Segment) null, prepended(bends, (Point2D) b.getPoint().clone()), a); 397 398 } 398 399 399 400 private double getFromOffset() { 400 401 return prev == null ? 0 : prev.getFromOffset() + prev.length; 401 402 } 402 403 403 404 public double getOffset(double x, double y) { 404 405 return getOffsetInternal(new Point2D.Double(x, y), -1, Double.POSITIVE_INFINITY); 405 406 } 406 407 407 408 private double getOffsetInternal(Point2D p, double offset, double quality) { 408 409 final Point2D closest = closest(new Line2D.Double(from, to), p); 409 410 final double myQuality = closest.distance(p); 410 411 411 412 if (myQuality < quality) { 412 413 quality = myQuality; 413 414 414 415 final Line2D normal = line(p, angle + PI / 2); 415 416 final Point2D isect = intersection(normal, new Line2D.Double(from, to)); 416 417 final double d = from.distance(isect); 417 418 final boolean negative = Math.abs(angle(from, isect) - angle) > 1; 418 419 419 420 offset = getFromOffset() + (negative ? -1 : 1) * d; 420 421 } 421 422 422 423 return next == null ? offset : next.getOffsetInternal(p, offset, quality); 423 424 } 424 425 425 426 public Path append(Path path, boolean forward, double offset) { 426 427 if (ROUND_CORNERS) { … … 428 429 final Point2D s = forward ? to : from; 429 430 final Point2D e = forward ? from : to; 430 431 431 432 if (n == null) { 432 433 return path.lineTo(e.getX(), e.getY(), length - offset); 433 434 } 434 435 435 436 final double a = minAngleDiff(angle, n.angle); 436 437 final double d = 3 * outerMargin + getWidth(getModel().getToEnd(), (forward && a < 0) || (!forward && a > 0)); 437 438 final double l = d * tan(abs(a)); 438 439 439 440 if (length - offset < l / 2 || n.length < l / 2) { 440 441 return n.append(path.lineTo(e.getX(), e.getY(), length - offset), forward, 0); 441 442 } else { 442 443 final Point2D p = relativePoint(e, l / 2, angle(e, s)); 443 444 444 445 final Path line = path.lineTo(p.getX(), p.getY(), length - l / 2 - offset); 445 446 final Path curve = line.curveTo(d, d, a, l); 446 447 447 448 return n.append(curve, forward, l / 2); 448 449 } … … 456 457 } 457 458 } 458 459 459 460 /** 460 461 * This should become a setting, but rounding is (as of yet) still slightly buggy and a low … … 462 463 */ 463 464 private static final boolean ROUND_CORNERS = false; 464 465 private static finalList<Point2D> prepended(List<Point2D> bends, Point2D point) {465 466 private static List<Point2D> prepended(List<Point2D> bends, Point2D point) { 466 467 final List<Point2D> result = new ArrayList<>(bends.size() + 1); 467 468 result.add(point); … … 469 470 return result; 470 471 } 471 472 472 473 private final GuiContainer container; 473 474 private final double innerMargin; 474 475 private final double outerMargin; 475 476 476 477 private final float lineWidth; 477 478 private final Stroke regularStroke; 478 479 private final Stroke dashedStroke; 479 480 480 481 private final JunctionGui a; 481 482 private final JunctionGui b; 482 483 private final double length; 483 484 484 485 private final IncomingConnector incomingA; 485 486 private final IncomingConnector incomingB; 486 487 487 488 private final Road road; 488 489 private final List<Segment> segments = new ArrayList<>(); 489 490 490 491 final double connectorRadius; 491 492 publicRoadGui(GuiContainer container, Road road) {492 493 RoadGui(GuiContainer container, Road road) { 493 494 this.container = container; 494 495 495 496 this.road = road; 496 497 497 498 this.a = container.getGui(road.getFromEnd().getJunction()); 498 499 this.b = container.getGui(road.getToEnd().getJunction()); 499 500 500 501 this.incomingA = new IncomingConnector(road.getFromEnd()); 501 502 this.incomingB = new IncomingConnector(road.getToEnd()); 502 503 503 504 final List<Point2D> bends = new ArrayList<>(); 504 505 final List<Node> nodes = road.getRoute().getNodes(); … … 506 507 bends.add(container.translateAndScale(loc(nodes.get(i)))); 507 508 } 508 509 509 510 // they add themselves to this.segments 510 511 new Segment(b, bends, a); … … 514 515 } 515 516 this.length = l; 516 517 517 518 this.innerMargin = !incomingA.getLanes().isEmpty() && !incomingB.getLanes().isEmpty() ? 1 * container 518 519 .getLaneWidth() / 15 : 0; … … 525 526 }, 0); 526 527 } 527 528 528 529 public JunctionGui getA() { 529 530 return a; 530 531 } 531 532 532 533 public JunctionGui getB() { 533 534 return b; 534 535 } 535 536 536 537 public Line2D getLeftCurb(Road.End end) { 537 538 return GuiUtil.line(getCorner(end, true), getAngle(end) + PI); 538 539 } 539 540 540 541 public Line2D getRightCurb(Road.End end) { 541 542 return GuiUtil.line(getCorner(end, false), getAngle(end) + PI); 542 543 } 543 544 544 545 private Point2D getLeftCorner(Road.End end) { 545 546 return getCorner(end, true); 546 547 } 547 548 548 549 private Point2D getRightCorner(Road.End end) { 549 550 return getCorner(end, false); 550 551 } 551 552 552 553 private Point2D getCorner(Road.End end, boolean left) { 553 554 final JunctionGui j = end.isFromEnd() ? a : b; … … 556 557 final double a = getAngle(end) + PI; 557 558 final double t = left ? j.getLeftTrim(end) : j.getRightTrim(end); 558 559 559 560 final double dx = s * cos(PI / 2 - a) * w + cos(a) * t; 560 561 final double dy = s * sin(PI / 2 - a) * w - sin(a) * t; 561 562 562 563 return new Point2D.Double(j.x + dx, j.y + dy); 563 564 } 564 565 565 566 private double getWidth(Road.End end, boolean left) { 566 567 if (!end.getRoad().equals(road)) { 567 568 throw new IllegalArgumentException(); 568 569 } 569 570 570 571 final int lcForward = incomingA.getLanes().size(); 571 572 final int lcBackward = incomingB.getLanes().size(); 572 573 573 574 final double LW = getContainer().getLaneWidth(); 574 575 final double M = innerMargin + outerMargin; 575 576 576 577 if (end.isToEnd()) { 577 578 return (left ? lcBackward : lcForward) * LW + M; … … 580 581 } 581 582 } 582 583 583 584 List<InteractiveElement> paint(Graphics2D g2d) { 584 585 final List<InteractiveElement> result = new ArrayList<>(); 585 586 586 587 result.addAll(paintLanes(g2d)); 587 588 588 589 if (getModel().isPrimary()) { 589 590 result.add(new ViaConnector(getModel().getFromEnd())); … … 594 595 result.addAll(extenders(getModel().getToEnd())); 595 596 } 596 597 597 598 g2d.setColor(Color.RED); 598 599 for (Segment s : segments) { 599 600 g2d.fill(new Ellipse2D.Double(s.from.getX() - 1, s.from.getY() - 1, 2, 2)); 600 601 } 601 602 602 603 return result; 603 604 } 604 605 605 606 private List<LaneAdder> laneAdders() { 606 607 final List<LaneAdder> result = new ArrayList<>(4); 607 608 608 609 if (!incomingA.getLanes().isEmpty()) { 609 610 result.add(new LaneAdder(getModel().getToEnd(), Lane.Kind.EXTRA_LEFT)); 610 611 result.add(new LaneAdder(getModel().getToEnd(), Lane.Kind.EXTRA_RIGHT)); 611 612 } 612 613 613 614 if (!incomingB.getLanes().isEmpty()) { 614 615 result.add(new LaneAdder(getModel().getFromEnd(), Lane.Kind.EXTRA_LEFT)); 615 616 result.add(new LaneAdder(getModel().getFromEnd(), Lane.Kind.EXTRA_RIGHT)); 616 617 } 617 618 618 619 return result; 619 620 } 620 621 621 622 private List<Extender> extenders(Road.End end) { 622 623 if (!end.isExtendable()) { 623 624 return Collections.emptyList(); 624 625 } 625 626 626 627 final List<Extender> result = new ArrayList<>(); 627 628 628 629 final Node n = end.getJunction().getNode(); 629 630 for (Way w : OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)) { … … 634 635 } 635 636 } 636 637 637 638 return result; 638 639 } 639 640 640 641 public Road getModel() { 641 642 return road; 642 643 } 643 644 644 645 public IncomingConnector getConnector(Road.End end) { 645 646 return end.isFromEnd() ? incomingA : incomingB; 646 647 } 647 648 648 649 private List<InteractiveElement> paintLanes(Graphics2D g2d) { 649 650 final Path2D middleLines = new Path2D.Double(); 650 651 651 652 g2d.setStroke(regularStroke); 652 653 653 654 final boolean forward = !incomingA.getLanes().isEmpty(); 654 655 final boolean backward = !incomingB.getLanes().isEmpty(); 655 656 656 657 final Path2D middleArea; 657 658 if (forward && backward) { 658 659 paintLanes(g2d, middleLines, true); 659 660 paintLanes(g2d, middleLines, false); 660 661 661 662 middleLines.closePath(); 662 663 middleArea = middleLines; … … 664 665 } else if (forward || backward) { 665 666 paintLanes(g2d, middleLines, forward); 666 667 667 668 middleArea = new Path2D.Double(); 668 669 middleArea.append(middleLines.getPathIterator(null), false); … … 673 674 throw new AssertionError(); 674 675 } 675 676 676 677 g2d.fill(middleArea); 677 678 g2d.setColor(Color.WHITE); 678 679 g2d.draw(middleLines); 679 680 680 681 final List<InteractiveElement> result = new ArrayList<>(); 681 682 682 683 moveIncoming(getModel().getFromEnd()); 683 684 moveIncoming(getModel().getToEnd()); 684 685 result.add(incomingA); 685 686 result.add(incomingB); 686 687 687 688 for (IncomingConnector c : Arrays.asList(incomingA, incomingB)) { 688 689 int offset = 0; 689 690 for (LaneGui l : c.getLanes()) { 690 691 moveOutgoing(l, offset++); 691 692 692 693 result.add(l.outgoing); 693 694 if (l.getModel().isExtra()) { … … 696 697 } 697 698 } 698 699 699 700 return result; 700 701 } 701 702 702 703 private void paintLanes(Graphics2D g2d, Path2D middleLines, boolean forward) { 703 704 final Shape clip = clip(); 704 705 g2d.clip(clip); 705 706 706 707 final Path middle = middlePath(forward); 707 708 708 709 Path innerPath = middle.offset(innerMargin, -1, -1, innerMargin); 709 710 final List<Path> linePaths = new ArrayList<>(); 710 711 linePaths.add(innerPath); 711 712 712 713 for (LaneGui l : forward ? incomingA.getLanes() : incomingB.getLanes()) { 713 714 l.setClip(clip); … … 715 716 linePaths.add(innerPath); 716 717 } 717 718 718 719 final Path2D area = new Path2D.Double(); 719 720 area(area, middle, innerPath.offset(outerMargin, -1, -1, outerMargin)); 720 721 g2d.setColor(Color.GRAY); 721 722 g2d.fill(area); 722 723 723 724 g2d.setColor(Color.WHITE); 724 725 final Path2D lines = new Path2D.Double(); 725 726 lines.append(innerPath.getIterator(), false); 726 727 g2d.draw(lines); 727 728 728 729 // g2d.setColor(new Color(32, 128, 192)); 729 730 g2d.setColor(Color.WHITE); … … 735 736 } 736 737 g2d.setStroke(regularStroke); 737 738 738 739 // g2d.setColor(new Color(32, 128, 192)); 739 740 // lines.reset(); 740 741 // lines.append(middle.getIterator(), false); 741 742 // g2d.draw(lines); 742 743 743 744 g2d.setClip(null); 744 745 } 745 746 746 747 private Shape clip() { 747 748 final Area clip = new Area(new Rectangle2D.Double(-100000, -100000, 200000, 200000)); 748 749 clip.subtract(new Area(negativeClip(true))); 749 750 clip.subtract(new Area(negativeClip(false))); 750 751 751 752 return clip; 752 753 } 753 754 754 755 private Shape negativeClip(boolean forward) { 755 756 final Road.End end = forward ? getModel().getToEnd() : getModel().getFromEnd(); 756 757 final JunctionGui j = forward ? b : a; 757 758 758 759 final Line2D lc = getLeftCurb(end); 759 760 final Line2D rc = getRightCurb(end); 760 761 761 762 final Path2D negativeClip = new Path2D.Double(); 762 763 763 764 final double d = rc.getP1().distance(j.getPoint()) + lc.getP1().distance(j.getPoint()); 764 765 765 766 final double cm = 0.01 / getContainer().getMpp(); // 1 centimeter 766 767 final double rca = angle(rc) + PI; … … 770 771 final Point2D l1 = relativePoint(relativePoint(lc.getP1(), 1, angle(rc.getP1(), lc.getP1())), cm, lca); 771 772 final Point2D l2 = relativePoint(l1, d, lca); 772 773 773 774 negativeClip.moveTo(r1.getX(), r1.getY()); 774 775 negativeClip.lineTo(r2.getX(), r2.getY()); … … 776 777 negativeClip.lineTo(l1.getX(), l1.getY()); 777 778 negativeClip.closePath(); 778 779 779 780 return negativeClip; 780 781 } 781 782 782 783 public Path getLaneMiddle(boolean forward) { 783 784 final Path mid = middlePath(!forward); 784 785 final double w = getWidth(forward ? getModel().getFromEnd() : getModel().getToEnd(), true); 785 786 final double o = (w - outerMargin) / 2; 786 787 787 788 return o > 0 ? mid.offset(-o, -1, -1, -o) : mid; 788 789 } 789 790 790 791 private Path middlePath(boolean forward) { 791 792 final Path path = forward ? Path.create(b.x, b.y) : Path.create(a.x, a.y); 792 793 final Segment first = forward ? segments.get(segments.size() - 1) : segments.get(0); 793 794 794 795 return first.append(path, forward, 0); 795 796 } 796 797 797 798 private void moveIncoming(Road.End end) { 798 799 final Point2D lc = getLeftCorner(end); 799 800 final Point2D rc = getRightCorner(end); 800 801 final Line2D cornerLine = new Line2D.Double(lc, rc); 801 802 802 803 final double a = getAngle(end); 803 804 final Line2D roadLine = line(getContainer().getGui(end.getJunction()).getPoint(), a); 804 805 805 806 final Point2D i = intersection(roadLine, cornerLine); 806 807 // TODO fix depending on angle(i, lc) 807 808 final double offset = innerMargin + (getWidth(end, true) - innerMargin - outerMargin) / 2; 808 809 final Point2D loc = relativePoint(i, offset, angle(i, lc)); 809 810 810 811 getConnector(end).move(loc.getX(), loc.getY()); 811 812 } 812 813 813 814 private void moveOutgoing(LaneGui lane, int offset) { 814 815 final Road.End end = lane.getModel().getOutgoingRoadEnd(); 815 816 816 817 final Point2D lc = getLeftCorner(end); 817 818 final Point2D rc = getRightCorner(end); 818 819 final Line2D cornerLine = new Line2D.Double(lc, rc); 819 820 820 821 final double a = getAngle(end); 821 822 final Line2D roadLine = line(getContainer().getGui(end.getJunction()).getPoint(), a); 822 823 823 824 final Point2D i = intersection(roadLine, cornerLine); 824 825 // TODO fix depending on angle(i, rc) 825 826 final double d = innerMargin + (2 * offset + 1) * getContainer().getLaneWidth() / 2; 826 827 final Point2D loc = relativePoint(i, d, angle(i, rc)); 827 828 828 829 lane.outgoing.move(loc.getX(), loc.getY()); 829 830 } 830 831 831 832 public JunctionGui getJunction(Road.End end) { 832 833 if (!getModel().equals(end.getRoad())) { 833 834 throw new IllegalArgumentException(); 834 835 } 835 836 836 837 return end.isFromEnd() ? getA() : getB(); 837 838 } 838 839 839 840 public double getAngle(Road.End end) { 840 841 if (!getModel().equals(end.getRoad())) { 841 842 throw new IllegalArgumentException(); 842 843 } 843 844 844 845 if (end.isToEnd()) { 845 846 return segments.get(segments.size() - 1).angle; … … 849 850 } 850 851 } 851 852 852 853 public double getWidth(Road.End end) { 853 854 return getWidth(end, true) + getWidth(end, false); 854 855 } 855 856 856 857 public double getLength() { 857 858 return length; 858 859 } 859 860 860 861 public double getOffset(double x, double y) { 861 862 return segments.get(0).getOffset(x, y); 862 863 } 863 864 864 865 public GuiContainer getContainer() { 865 866 return container; 866 867 } 867 868 868 869 public List<LaneGui> getLanes() { 869 870 final List<LaneGui> result = new ArrayList<>(); 870 871 871 872 result.addAll(incomingB.getLanes()); 872 873 result.addAll(incomingA.getLanes()); 873 874 874 875 return Collections.unmodifiableList(result); 875 876 } 876 877 877 878 public List<LaneGui> getLanes(Road.End end) { 878 879 return getConnector(end.getOppositeEnd()).getLanes(); -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/State.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 14 15 static class AllTurns extends State { 15 16 private final State wrapped; 16 17 publicAllTurns(State wrapped) {17 18 AllTurns(State wrapped) { 18 19 this.wrapped = wrapped; 19 20 } 20 21 21 22 public State unwrap() { 22 23 return wrapped; 23 24 } 24 25 25 26 @Override 26 27 State carryOver(GuiContainer newContainer) { … … 28 29 } 29 30 } 30 31 31 32 static class Connecting extends State { 32 33 private final Lane lane; 33 34 private final List<RoadGui.ViaConnector> vias; 34 35 publicConnecting(Lane lane) {36 this(lane, Collections.<RoadGui.ViaConnector> 37 } 38 39 publicConnecting(Lane lane, List<ViaConnector> vias) {35 36 Connecting(Lane lane) { 37 this(lane, Collections.<RoadGui.ViaConnector>emptyList()); 38 } 39 40 Connecting(Lane lane, List<ViaConnector> vias) { 40 41 this.lane = lane; 41 42 this.vias = vias; 42 43 } 43 44 44 45 public Connecting next(RoadGui.ViaConnector via) { 45 46 if (vias.isEmpty()) { 46 47 return new Connecting(lane, Collections.unmodifiableList(Arrays.asList(via))); 47 48 } 48 49 49 50 final List<RoadGui.ViaConnector> tmp = new ArrayList<>(vias.size() + 1); 50 51 final boolean even = (vias.size() & 1) == 0; 51 52 final RoadGui.ViaConnector last = vias.get(vias.size() - 1); 52 53 53 54 if (last.equals(via) || !even && last.getRoadEnd().getJunction().equals(via.getRoadEnd().getJunction())) { 54 55 return pop().next(via); 55 56 } 56 57 57 58 if (vias.size() >= 2) { 58 59 if (lane.getOutgoingJunction().equals(via.getRoadEnd().getJunction())) { … … 62 63 } 63 64 } 64 65 65 66 for (RoadGui.ViaConnector v : vias) { 66 67 tmp.add(v); 67 68 68 69 if (!(even && v.equals(last)) && v.getRoadEnd().getJunction().equals(via.getRoadEnd().getJunction())) { 69 70 return new Connecting(lane, Collections.unmodifiableList(tmp)); 70 71 } 71 72 } 72 73 73 74 tmp.add(via); 74 75 return new Connecting(lane, Collections.unmodifiableList(tmp)); 75 76 } 76 77 77 78 public Junction getJunction() { 78 79 return vias.isEmpty() ? lane.getOutgoingJunction() : vias.get(vias.size() - 1).getRoadEnd().getJunction(); 79 80 } 80 81 81 82 public RoadGui.ViaConnector getBacktrackViaConnector() { 82 83 return vias.size() < 2 ? null : vias.get(vias.size() - 2); 83 84 } 84 85 85 86 public List<RoadGui.ViaConnector> getViaConnectors() { 86 87 return vias; 87 88 } 88 89 89 90 public Lane getLane() { 90 91 return lane; 91 92 } 92 93 93 94 public Connecting pop() { 94 95 return new Connecting(lane, vias.subList(0, vias.size() - 1)); 95 96 } 96 97 } 97 98 98 99 static class Invalid extends State { 99 100 private final State wrapped; 100 101 publicInvalid(State wrapped) {101 102 Invalid(State wrapped) { 102 103 this.wrapped = wrapped; 103 104 } 104 105 105 106 public State unwrap() { 106 107 return wrapped; 107 108 } 108 109 } 109 110 110 111 static class Dirty extends State { 111 112 private final State wrapped; 112 113 publicDirty(State wrapped) {113 114 Dirty(State wrapped) { 114 115 this.wrapped = wrapped; 115 116 } 116 117 117 118 public State unwrap() { 118 119 return wrapped; 119 120 } 120 121 121 122 @Override 122 123 State carryOver(GuiContainer newContainer) { … … 124 125 } 125 126 } 126 127 127 128 static class Default extends State { 128 publicDefault() {}129 } 130 129 Default() {} 130 } 131 131 132 static class IncomingActive extends State { 132 133 private final Road.End roadEnd; 133 134 publicIncomingActive(Road.End roadEnd) {134 135 IncomingActive(Road.End roadEnd) { 135 136 this.roadEnd = roadEnd; 136 137 } 137 138 138 139 public Road.End getRoadEnd() { 139 140 return roadEnd; 140 141 } 141 142 142 143 @Override 143 144 State carryOver(GuiContainer newContainer) { … … 145 146 return this; 146 147 } 147 148 148 149 final Junction newJunction = newContainer.getModel().getJunction(roadEnd.getJunction().getNode()); 149 150 150 151 for (Road.End e : newJunction.getRoadEnds()) { 151 152 if (e.isToEnd() && e.getWay().equals(roadEnd.getWay())) { … … 153 154 } 154 155 } 155 156 156 157 return new Default(); 157 158 } 158 159 } 159 160 160 161 static class OutgoingActive extends State { 161 162 private final LaneGui lane; 162 163 publicOutgoingActive(LaneGui lane) {163 164 OutgoingActive(LaneGui lane) { 164 165 this.lane = lane; 165 166 } 166 167 167 168 public LaneGui getLane() { 168 169 return lane; 169 170 } 170 171 171 172 @Override 172 173 State delete() { … … 174 175 return this; 175 176 } 176 177 177 178 lane.getModel().remove(); 178 179 179 180 return new Invalid(this); 180 181 } 181 182 182 183 @Override 183 184 State carryOver(GuiContainer newContainer) { … … 185 186 return this; 186 187 } 187 188 188 189 final Lane model = lane.getModel(); 189 190 final Junction newJunction = newContainer.getModel().getJunction(model.getOutgoingJunction().getNode()); 190 191 191 192 for (Road.End e : newJunction.getRoadEnds()) { 192 193 if (e.isToEnd() && e.getWay().equals(model.getOutgoingRoadEnd().getWay())) { … … 196 197 } 197 198 } 198 199 199 200 break; 200 201 } 201 202 } 202 203 203 204 return new Default(); 204 205 } 205 206 } 206 207 207 208 State delete() { 208 209 return this; 209 210 } 210 211 211 212 State carryOver(GuiContainer newContainer) { 212 213 return this; -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/TurnLanesDialog.java
r32458 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 42 43 private class EditAction extends JosmAction { 43 44 private static final long serialVersionUID = 4114119073563457706L; 44 45 publicEditAction() {45 46 EditAction() { 46 47 super(tr("Edit"), "dialogs/edit", tr("Edit turn relations and lane lengths for selected node."), null, 47 48 false); 48 49 } 49 50 50 51 @Override 51 52 public void actionPerformed(ActionEvent e) { … … 57 58 } 58 59 } 59 60 60 61 private class ValidateAction extends JosmAction { 61 62 private static final long serialVersionUID = 7510740945725851427L; 62 63 publicValidateAction() {63 64 ValidateAction() { 64 65 super(tr("Validate"), "dialogs/validator", tr("Validate turn- and lane-length-relations for consistency."), 65 66 null, false); 66 67 } 67 68 68 69 @Override 69 70 public void actionPerformed(ActionEvent e) { … … 74 75 } 75 76 } 76 77 77 78 private final DataSetListener dataSetListener = new DataSetListener() { 78 79 @Override … … 80 81 refresh(); 81 82 } 82 83 83 84 @Override 84 85 public void tagsChanged(TagsChangedEvent event) { 85 86 refresh(); 86 87 } 88 87 88 } 89 89 90 @Override 90 91 public void relationMembersChanged(RelationMembersChangedEvent event) { 91 92 refresh(); 92 93 } 93 94 94 95 @Override 95 96 public void primitivesRemoved(PrimitivesRemovedEvent event) { 96 97 refresh(); 97 98 } 98 99 99 100 @Override 100 101 public void primitivesAdded(PrimitivesAddedEvent event) { 101 102 refresh(); 102 103 } 103 104 104 105 @Override 105 106 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 106 107 refresh(); 107 108 } 108 109 109 110 @Override 110 111 public void nodeMoved(NodeMovedEvent event) { 111 112 refresh(); 112 113 } 113 114 114 115 @Override 115 116 public void dataChanged(DataChangedEvent event) { 116 117 refresh(); 117 118 } 118 119 119 120 private void refresh() { 120 121 if (editing) { … … 123 124 } 124 125 }; 125 126 126 127 private final JosmAction editAction = new EditAction(); 127 128 private final JosmAction validateAction = new ValidateAction(); 128 129 129 130 private static final long serialVersionUID = -1998375221636611358L; 130 131 131 132 private static final String CARD_EDIT = "EDIT"; 132 133 private static final String CARD_VALIDATE = "VALIDATE"; 133 134 134 135 private final JPanel body = new JPanel(); 135 136 private final JunctionPane junctionPane = new JunctionPane(GuiContainer.empty()); 136 137 137 138 private final JToggleButton editButton = new JToggleButton(editAction); 138 139 private final JToggleButton validateButton = new JToggleButton(validateAction); 139 140 140 141 private final Set<OsmPrimitive> selected = new HashSet<>(); 141 142 142 143 private boolean editing = true; 143 144 private boolean wasShowing = false; 144 145 145 146 public TurnLanesDialog() { 146 147 super(tr("Turn Lanes"), "turnlanes.png", tr("Edit turn lanes"), null, 200); 147 148 148 149 Main.getLayerManager().addActiveLayerChangeListener(this); 149 150 DataSet.addSelectionListener(this); 150 151 151 152 final JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 4, 4)); 152 153 final ButtonGroup group = new ButtonGroup(); … … 155 156 buttonPanel.add(editButton); 156 157 buttonPanel.add(validateButton); 157 158 158 159 body.setLayout(new CardLayout(4, 4)); 159 160 160 161 add(buttonPanel, BorderLayout.SOUTH); 161 162 add(body, BorderLayout.CENTER); 162 163 163 164 body.add(junctionPane, CARD_EDIT); 164 165 body.add(new ValidationPanel(), CARD_VALIDATE); 165 166 166 167 editButton.doClick(); 167 168 } 168 169 169 170 @Override 170 171 protected void stateChanged() { … … 174 175 wasShowing = isShowing; 175 176 } 176 177 177 178 void refresh() { 178 179 if (isShowing && editing) { … … 180 181 final List<Node> nodes = OsmPrimitive.getFilteredList(s, Node.class); 181 182 final List<Way> ways = OsmPrimitive.getFilteredList(s, Way.class); 182 183 183 184 final ModelContainer mc = nodes.isEmpty() ? ModelContainer.empty() : ModelContainer 184 185 .createEmpty(nodes, ways); 185 186 186 187 junctionPane.setJunction(new GuiContainer(mc)); 187 188 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ValidationPanel.java
r32458 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.gui; 2 3 … … 26 27 class ValidationPanel extends JPanel { 27 28 private static final long serialVersionUID = -1585778734201458665L; 28 29 29 30 private static final String[] COLUMN_NAMES = { 30 31 tr("Description"), tr("Type"), tr("Quick-Fix") 31 32 }; 32 33 33 34 private final Action refreshAction = new JosmAction(tr("Refresh"), "dialogs/refresh", 34 35 tr("Revalidate all turnlanes-relations."), null, false) { 35 36 private static final long serialVersionUID = -8110599654128234810L; 36 37 37 38 @Override 38 39 public void actionPerformed(ActionEvent e) { … … 40 41 } 41 42 }; 42 43 43 44 private final Action fixAction = new JosmAction(tr("Fix"), "dialogs/fix", tr("Automatically fixes the issue."), null, 44 45 false) { 45 46 private static final long serialVersionUID = -8110599654128234810L; 46 47 47 48 @Override 48 49 public void actionPerformed(ActionEvent e) { … … 54 55 } 55 56 }; 56 57 57 58 private final Action selectAction = new JosmAction(tr("Select"), "dialogs/select", 58 59 tr("Selects the offending relation."), null, false) { 59 60 private static final long serialVersionUID = -8110599654128234810L; 60 61 61 62 @Override 62 63 public void actionPerformed(ActionEvent e) { … … 68 69 } 69 70 }; 70 71 71 72 private final SideButton refreshButton = new SideButton(refreshAction); 72 73 private final SideButton fixButton = new SideButton(fixAction); 73 74 private final SideButton selectButton = new SideButton(selectAction); 74 75 75 76 private final DefaultTableModel issueModel = new DefaultTableModel(COLUMN_NAMES, 0); 76 77 private final List<Issue> issues = new ArrayList<>(); 77 78 private final JTable issueTable = new JTable(issueModel) { 78 79 private static final long serialVersionUID = 6323348290180585298L; 79 80 81 @Override 80 82 public boolean isCellEditable(int row, int column) { 81 83 return false; 82 } ;84 } 83 85 }; 84 86 85 87 private Issue selected; 86 87 publicValidationPanel() {88 89 ValidationPanel() { 88 90 super(new BorderLayout(4, 4)); 89 91 90 92 final JPanel buttonPanel = new JPanel(new GridLayout(1, 3, 4, 4)); 91 93 92 94 buttonPanel.add(refreshButton); 93 95 buttonPanel.add(fixButton); 94 96 buttonPanel.add(selectButton); 95 97 96 98 add(buttonPanel, BorderLayout.NORTH); 97 99 add(new JScrollPane(issueTable), BorderLayout.CENTER); 98 100 99 101 issueTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 100 102 issueTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { … … 103 105 final int i = issueTable.getSelectedRow(); 104 106 final Issue issue = i >= 0 ? issues.get(i) : null; 105 107 106 108 setSelected(issue); 107 109 } 108 110 }); 109 111 110 112 setSelected(null); 111 113 } 112 114 113 115 private void setIssues(List<Issue> issues) { 114 116 issueModel.setRowCount(0); 115 117 this.issues.clear(); 116 118 117 119 for (Issue i : issues) { 118 120 final String[] row = { … … 125 127 } 126 128 } 127 129 128 130 private void setSelected(Issue selected) { 129 131 this.selected = selected; 130 132 131 133 if (selected == null) { 132 134 fixButton.setEnabled(false); -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Constants.java
r26154 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 7 8 String SPLIT_REGEX = "\\p{Zs}*[,:;]\\p{Zs}*"; 8 9 Pattern SPLIT_PATTERN = Pattern.compile(SPLIT_REGEX); 9 10 10 11 String TYPE_LENGTHS = "turnlanes:lengths"; 11 12 12 13 String LENGTHS_KEY_LENGTHS_LEFT = "lengths:left"; 13 14 String LENGTHS_KEY_LENGTHS_RIGHT = "lengths:right"; 14 15 15 16 String TYPE_TURNS = "turnlanes:turns"; 16 17 17 18 String TURN_ROLE_VIA = "via"; 18 19 String TURN_ROLE_FROM = "from"; 19 20 String TURN_ROLE_TO = "to"; 20 21 21 22 String TURN_KEY_LANES = "lanes"; 22 23 String TURN_KEY_EXTRA_LANES = "lanes:extra"; 23 24 String LENGTHS_ROLE_END = "end"; 24 25 String LENGTHS_ROLE_WAYS = "ways"; 25 26 26 27 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/GenericCommand.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 17 18 final PrimitiveData before; 18 19 final AbstractPrimitive after; 19 20 publicBeforeAfter(PrimitiveData before, AbstractPrimitive after) {20 21 BeforeAfter(PrimitiveData before, AbstractPrimitive after) { 21 22 this.before = before; 22 23 this.after = after; 23 24 } 24 25 25 26 public OsmPrimitive afterPrimitive() { 26 27 return (OsmPrimitive) after; 27 28 } 28 29 29 30 public PrimitiveData afterData() { 30 31 return (PrimitiveData) after; 31 32 } 32 33 } 33 34 34 35 private final DataSet dataSet; 35 36 private final String description; 36 37 private final Map<OsmPrimitive, BeforeAfter> beforeAfters = new HashMap<>(); 37 38 38 39 public GenericCommand(DataSet dataSet, String description) { 39 40 this.dataSet = dataSet; 40 41 this.description = description; 41 42 } 42 43 43 44 void add(OsmPrimitive p) { 44 45 beforeAfters.put(p, new BeforeAfter(null, p)); 45 46 } 46 47 47 48 AbstractPrimitive backup(OsmPrimitive p) { 48 49 final BeforeAfter ba = beforeAfters.get(p); 49 50 50 51 if (ba == null) { 51 52 final BeforeAfter newBa = new BeforeAfter(p.save(), p.save()); … … 56 57 } 57 58 } 58 59 59 60 @Override 60 61 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, 61 62 Collection<OsmPrimitive> added) {} 62 63 63 64 @Override 64 65 public String getDescriptionText() { 65 66 return description; 66 67 } 67 68 68 69 @Override 69 70 public boolean executeCommand() { … … 77 78 return true; 78 79 } 79 80 80 81 @Override 81 82 public void undoCommand() { … … 88 89 } 89 90 } 90 91 91 92 @Override 92 93 public PrimitiveData getOrig(OsmPrimitive osm) { 93 94 return beforeAfters.get(osm).before; 94 95 } 95 96 96 97 @Override 97 98 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() { -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Issue.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 11 12 import org.openstreetmap.josm.data.osm.Relation; 12 13 13 public class Issue {14 public final class Issue { 14 15 public enum Severity { 15 16 INFO, … … 17 18 ERROR; 18 19 } 19 20 public static abstractclass QuickFix {20 21 public abstract static class QuickFix { 21 22 public static final QuickFix NONE = new QuickFix(tr("None")) { 22 23 23 24 @Override 24 25 public boolean perform() { … … 26 27 } 27 28 }; 28 29 29 30 private final String description; 30 31 31 32 public QuickFix(String description) { 32 33 this.description = description; 33 34 } 34 35 35 36 public String getDescription() { 36 37 return description; 37 38 } 38 39 39 40 public abstract boolean perform(); 40 41 } 41 42 42 43 private final Severity severity; 43 44 private final Relation relation; … … 45 46 private final String description; 46 47 private final QuickFix quickFix; 47 48 48 49 private Issue(Severity severity, Relation relation, List<? extends OsmPrimitive> primitives, String description, 49 50 QuickFix quickFix) { … … 54 55 this.quickFix = quickFix; 55 56 } 56 57 57 58 public static Issue newError(Relation relation, List<? extends OsmPrimitive> primitives, String description, 58 59 QuickFix quickFix) { 59 60 return new Issue(Severity.ERROR, relation, primitives, description, quickFix); 60 61 } 61 62 62 63 public static Issue newError(Relation relation, List<? extends OsmPrimitive> primitives, String description) { 63 64 return newError(relation, primitives, description, QuickFix.NONE); 64 65 } 65 66 66 67 public static Issue newError(Relation relation, OsmPrimitive primitive, String description) { 67 68 return newError(relation, Arrays.asList(primitive), description, QuickFix.NONE); 68 69 } 69 70 70 71 public static Issue newError(Relation relation, String description) { 71 return newError(relation, Collections.<OsmPrimitive> 72 return newError(relation, Collections.<OsmPrimitive>emptyList(), description, QuickFix.NONE); 72 73 } 73 74 74 75 public static Issue newWarning(List<OsmPrimitive> primitives, String description) { 75 76 return new Issue(Severity.WARN, null, primitives, description, QuickFix.NONE); 76 77 } 77 78 78 79 public Severity getSeverity() { 79 80 return severity; 80 81 } 81 82 82 83 public String getDescription() { 83 84 return description; 84 85 } 85 86 86 87 public Relation getRelation() { 87 88 return relation; 88 89 } 89 90 90 91 public List<OsmPrimitive> getPrimitives() { 91 92 return primitives; 92 93 } 93 94 94 95 public QuickFix getQuickFix() { 95 96 return quickFix; -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Junction.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 11 12 public class Junction { 12 13 private final ModelContainer container; 13 14 14 15 private final Node node; 15 16 private final Set<Way> roads = new HashSet<>(); 16 17 17 18 Junction(ModelContainer container, Node n) { 18 19 this.container = container; 19 20 this.node = n; 20 21 } 21 22 22 23 public boolean isPrimary() { 23 24 return getContainer().isPrimary(this); 24 25 } 25 26 26 27 public Node getNode() { 27 28 return node; 28 29 } 29 30 30 31 public List<Road> getRoads() { 31 32 final List<Road> result = new ArrayList<>(roads.size()); 32 33 33 34 for (Way w : roads) { 34 35 result.add(container.getRoad(w)); 35 36 } 36 37 37 38 return result; 38 39 } 39 40 40 41 public List<Road.End> getRoadEnds() { 41 42 final List<Road.End> result = new ArrayList<>(roads.size()); 42 43 43 44 for (Way w : roads) { 44 45 result.add(getRoadEnd(w)); 45 46 } 46 47 47 48 return result; 48 49 } 49 50 50 51 void addRoad(Way w) { 51 52 roads.add(w); 52 53 } 53 54 54 55 Road.End getRoadEnd(Way w) { 55 56 final Road r = getContainer().getRoad(w); 56 57 57 58 if (r.getRoute().getSegments().size() == 1) { 58 59 final boolean starts = r.getRoute().getStart().equals(node); 59 60 final boolean ends = r.getRoute().getEnd().equals(node); 60 61 61 62 if (starts && ends) { 62 63 throw new IllegalArgumentException("Ambiguous: The way starts and ends at the junction node."); … … 71 72 return r.getToEnd(); 72 73 } 73 74 74 75 throw new IllegalArgumentException("While there exists a road for the given way, the way neither " 75 76 + "starts nor ends at the junction node."); 76 77 } 77 78 78 79 public ModelContainer getContainer() { 79 80 return container; -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Lane.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 20 21 EXTRA_RIGHT, 21 22 REGULAR; 22 23 23 24 public boolean isExtra() { 24 25 return this == EXTRA_LEFT || this == EXTRA_RIGHT; 25 26 } 26 27 } 27 28 28 29 static List<Lane> load(Road.End roadEnd) { 29 30 final List<Lane> result = new ArrayList<>(); 30 31 int i; 31 32 32 33 i = 0; 33 34 for (double l : roadEnd.getLengths(Kind.EXTRA_LEFT)) { … … 35 36 } 36 37 Collections.reverse(result); 37 38 38 39 final int regulars = getRegularCount(roadEnd.getWay(), roadEnd.getJunction().getNode()); 39 40 for (i = 1; i <= regulars; ++i) { 40 41 result.add(new Lane(roadEnd, i)); 41 42 } 42 43 43 44 i = 0; 44 45 for (double l : roadEnd.getLengths(Kind.EXTRA_RIGHT)) { 45 46 result.add(new Lane(roadEnd, ++i, Kind.EXTRA_RIGHT, l)); 46 47 } 47 48 48 49 return result; 49 50 } 50 51 51 52 static List<Double> loadLengths(Relation r, String key, double lengthBound) { 52 53 final List<Double> result = new ArrayList<>(); 53 54 54 55 if (r != null && r.get(key) != null) { 55 56 for (String s : Constants.SPLIT_PATTERN.split(r.get(key))) { … … 57 58 // a unit (m)) 58 59 final Double length = Double.parseDouble(s.trim()); 59 60 60 61 if (length >= lengthBound) { 61 62 result.add(length); … … 63 64 } 64 65 } 65 66 66 67 return result; 67 68 } 68 69 69 70 static int getRegularCount(Way w, Node end) { 70 71 final int count = Utils.parseIntTag(w, "lanes"); 71 72 final boolean forward = w.lastNode().equals(end); 72 73 73 74 if (w.hasDirectionKeys()) { 74 75 return getRegularCountOneWay(w, forward, count); … … 77 78 } 78 79 } 79 80 80 81 private static int getRegularCountOneWay(Way w, boolean forward, final int count) { 81 82 if (forward ^ "-1".equals(w.get("oneway"))) { … … 85 86 } 86 87 } 87 88 88 89 private static int getRegularCountTwoWay(Way w, boolean forward, final int count) { 89 90 if (w.get("lanes:backward") != null) { … … 91 92 return forward ? count - backwardCount : backwardCount; 92 93 } 93 94 94 95 if (w.get("lanes:forward") != null) { 95 96 final int forwardCount = Utils.parseIntTag(w, "lanes:forward"); 96 97 return forward ? forwardCount : count - forwardCount; 97 98 } 98 99 99 100 // default: round up in forward direction... 100 101 return forward ? (count + 1) / 2 : count / 2; 101 102 } 102 103 103 104 private final Road.End roadEnd; 104 105 private final int index; 105 106 private final Kind kind; 106 107 107 108 private Set<Turn> turns; 108 109 private double length = -1; 109 110 110 111 public Lane(Road.End roadEnd, int index) { 111 112 this.roadEnd = roadEnd; … … 113 114 this.kind = Kind.REGULAR; 114 115 } 115 116 116 117 public Lane(Road.End roadEnd, int index, Kind kind, double length) { 117 118 assert kind == Kind.EXTRA_LEFT || kind == Kind.EXTRA_RIGHT; 118 119 119 120 this.roadEnd = roadEnd; 120 121 this.index = index; 121 122 this.kind = kind; 122 123 this.length = length; 123 124 124 125 if (length <= 0) { 125 126 throw new IllegalArgumentException("Length must be positive"); 126 127 } 127 128 } 128 129 129 130 public Road getRoad() { 130 131 return roadEnd.getRoad(); 131 132 } 132 133 133 134 public Kind getKind() { 134 135 return kind; 135 136 } 136 137 137 138 public double getLength() { 138 139 return isExtra() ? length : getRoad().getLength(); 139 140 } 140 141 141 142 public void setLength(double length) { 142 143 if (!isExtra()) { … … 145 146 throw new IllegalArgumentException("Length must positive."); 146 147 } 147 148 148 149 this.length = length; 149 150 150 151 // TODO if needed, increase length of other lanes 151 152 getOutgoingRoadEnd().updateLengths(); 152 153 } 153 154 154 155 public boolean isExtra() { 155 156 return getKind() != Kind.REGULAR; 156 157 } 157 158 158 159 public int getIndex() { 159 160 return index; 160 161 } 161 162 162 163 public Junction getOutgoingJunction() { 163 164 return getOutgoingRoadEnd().getJunction(); 164 165 } 165 166 166 167 public Junction getIncomingJunction() { 167 168 return getIncomingRoadEnd().getJunction(); 168 169 } 169 170 170 171 public Road.End getOutgoingRoadEnd() { 171 172 return roadEnd; 172 173 } 173 174 174 175 public Road.End getIncomingRoadEnd() { 175 176 return roadEnd.getOppositeEnd(); 176 177 } 177 178 178 179 public ModelContainer getContainer() { 179 180 return getRoad().getContainer(); 180 181 } 181 182 182 183 public void addTurn(List<Road> via, Road.End to) { 183 184 final GenericCommand cmd = new GenericCommand(getOutgoingJunction().getNode().getDataSet(), tr("Add turn")); 184 185 185 186 Relation existing = null; 186 187 for (Turn t : to.getTurns()) { … … 190 191 return; 191 192 } 192 193 193 194 existing = t.getRelation(); 194 195 } 195 196 } 196 197 197 198 final Relation r; 198 199 if (existing == null) { 199 200 r = new Relation(); 200 201 r.put("type", Constants.TYPE_TURNS); 201 202 202 203 r.addMember(new RelationMember(Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay())); 203 204 if (via.isEmpty()) { … … 209 210 } 210 211 r.addMember(new RelationMember(Constants.TURN_ROLE_TO, to.getWay())); 211 212 212 213 cmd.add(r); 213 214 } else { 214 215 r = existing; 215 216 } 216 217 217 218 final String key = isExtra() ? Constants.TURN_KEY_EXTRA_LANES : Constants.TURN_KEY_LANES; 218 219 final List<Integer> lanes = Turn.indices(r, key); 219 220 lanes.add(getIndex()); 220 221 cmd.backup(r).put(key, Turn.join(lanes)); 221 222 222 223 Main.main.undoRedo.add(cmd); 223 224 } 224 225 225 226 public Set<Turn> getTurns() { 226 227 return turns; 227 228 } 228 229 229 230 public void remove() { 230 231 if (!isExtra()) { 231 232 throw new UnsupportedOperationException(); 232 233 } 233 234 234 235 final GenericCommand cmd = new GenericCommand(getOutgoingJunction().getNode().getDataSet(), tr("Delete lane.")); 235 236 236 237 for (Turn t : getTurns()) { 237 238 t.remove(cmd); 238 239 } 239 240 240 241 getOutgoingRoadEnd().removeLane(cmd, this); 241 242 242 243 Main.main.undoRedo.add(cmd); 243 244 } 244 245 245 246 void initialize() { 246 247 final Set<Turn> turns = Turn.load(getContainer(), Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay()); 247 248 248 249 final Iterator<Turn> it = turns.iterator(); 249 250 while (it.hasNext()) { 250 251 final Turn t = it.next(); 251 252 252 253 if (!t.getFrom().equals(this)) { 253 254 it.remove(); 254 255 } 255 256 } 256 257 257 258 this.turns = Collections.unmodifiableSet(turns); 258 259 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/ModelContainer.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 19 20 import org.openstreetmap.josm.tools.Pair; 20 21 21 public class ModelContainer {22 private static final ModelContainer EMPTY = new ModelContainer(Collections.<Node> 23 Collections.<Way> 24 22 public final class ModelContainer { 23 private static final ModelContainer EMPTY = new ModelContainer(Collections.<Node>emptySet(), 24 Collections.<Way>emptySet(), false); 25 25 26 public static ModelContainer create(Iterable<Node> primaryNodes, Iterable<Way> primaryWays) { 26 27 return new ModelContainer(new HashSet<>(CollectionUtils.toList(primaryNodes)), new HashSet<>( 27 28 CollectionUtils.toList(primaryWays)), false); 28 29 } 29 30 30 31 public static ModelContainer createEmpty(Iterable<Node> primaryNodes, Iterable<Way> primaryWays) { 31 32 return new ModelContainer(new HashSet<>(CollectionUtils.toList(primaryNodes)), new HashSet<>( 32 33 CollectionUtils.toList(primaryWays)), true); 33 34 } 34 35 35 36 public static ModelContainer empty() { 36 37 return EMPTY; 37 38 } 38 39 39 40 private static void close(Set<Node> closedNodes, Set<Way> closedWays) { 40 41 boolean closed = false; 41 42 42 43 while (!closed) { 43 44 closed = true; 44 45 45 46 for (Node n : new ArrayList<>(closedNodes)) { 46 47 for (Way w : Utils.filterRoads(n.getReferrers())) { … … 49 50 } 50 51 } 51 52 52 53 for (Way w : new ArrayList<>(closedWays)) { 53 54 closed &= close(closedNodes, closedWays, w); … … 56 57 } 57 58 } 58 59 59 60 private static boolean close(Set<Node> closedNodes, Set<Way> closedWays, Way w) { 60 61 boolean closed = true; 61 62 62 63 for (Relation r : OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class)) { 63 64 if (!r.get("type").equals(Constants.TYPE_TURNS)) { 64 65 continue; 65 66 } 66 67 67 68 for (RelationMember m : r.getMembers()) { 68 69 if (m.getRole().equals(Constants.TURN_ROLE_VIA) && m.getMember().equals(w)) { … … 71 72 } 72 73 } 73 74 74 75 return closed; 75 76 } 76 77 77 78 private static boolean close(Set<Node> closedNodes, Set<Way> closedWays, Relation r) { 78 79 boolean closed = true; 79 80 80 81 final List<Way> via = new ArrayList<>(); 81 82 for (RelationMember m : Utils.getMembers(r, Constants.TURN_ROLE_VIA)) { … … 87 88 } 88 89 } 89 90 90 91 if (!via.isEmpty()) { 91 92 final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM); 92 93 final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO); 93 94 94 95 closed &= !closedNodes.add(Utils.lineUp(from, via.get(0))); 95 96 closed &= !closedNodes.add(Utils.lineUp(via.get(via.size() - 1), to)); 96 97 } 97 98 98 99 return closed; 99 100 } 100 101 101 102 private static <E extends OsmPrimitive, C extends Collection<E>> C filterUsables(C collection) { 102 103 final Iterator<E> it = collection.iterator(); 103 104 104 105 while (it.hasNext()) { 105 106 final E e = it.next(); 106 107 107 108 if (e.getDataSet() == null || !e.isUsable()) { 108 109 it.remove(); 109 110 } 110 111 } 111 112 112 113 return collection; 113 114 } 114 115 115 116 private final Map<Node, Junction> junctions = new HashMap<>(); 116 117 private final Map<Way, Road> roads = new HashMap<>(); 117 118 118 119 private final Set<Node> primaryNodes; 119 120 private final Set<Way> primaryWays; 120 121 121 122 private final boolean empty; 122 123 123 124 private ModelContainer(Set<Node> primaryNodes, Set<Way> primaryWays, boolean empty) { 124 125 if (empty) { … … 129 130 final Set<Node> closedNodes = filterUsables(new HashSet<>(primaryNodes)); 130 131 final Set<Way> closedWays = filterUsables(new HashSet<>(primaryWays)); 131 132 132 133 close(closedNodes, closedWays); 133 134 134 135 this.primaryNodes = Collections.unmodifiableSet(closedNodes); 135 136 this.primaryWays = Collections.unmodifiableSet(closedWays); 136 137 137 138 for (Pair<Way, Junction> w : createPrimaryJunctions()) { 138 139 if (!this.primaryWays.contains(w.a)) { … … 140 141 } 141 142 } 142 143 143 144 for (Route r : Utils.orderWays(this.primaryWays, this.primaryNodes)) { 144 145 addRoad(new Road(this, r)); 145 146 } 146 147 147 148 for (Road r : roads.values()) { 148 149 r.initialize(); 149 150 } 150 151 151 152 this.empty = junctions.isEmpty(); 152 153 } 153 154 } 154 155 155 156 private Set<Pair<Way, Junction>> createPrimaryJunctions() { 156 157 final Set<Pair<Way, Junction>> roads = new HashSet<>(); 157 158 158 159 for (Node n : primaryNodes) { 159 160 final List<Way> ws = new ArrayList<>(); … … 163 164 } 164 165 } 165 166 166 167 if (ws.size() > 1) { 167 168 final Junction j = register(new Junction(this, n)); … … 171 172 } 172 173 } 173 174 174 175 return roads; 175 176 } 176 177 177 178 Junction getOrCreateJunction(Node n) { 178 179 final Junction existing = junctions.get(n); 179 180 180 181 if (existing != null) { 181 182 return existing; 182 183 } 183 184 184 185 return register(new Junction(this, n)); 185 186 } 186 187 187 188 public Junction getJunction(Node n) { 188 189 Junction j = junctions.get(n); 189 190 190 191 if (j == null) { 191 192 throw new IllegalArgumentException(); 192 193 } 193 194 194 195 return j; 195 196 } 196 197 197 198 Road getRoad(Way w) { 198 199 final Road r = roads.get(w); 199 200 200 201 if (r == null) { 201 202 throw new IllegalArgumentException("There is no road containing the given way."); 202 203 } 203 204 204 205 return r; 205 206 } 206 207 207 208 private void addRoad(Road newRoad, Road mergedA, Road mergedB) { 208 209 assert (mergedA == null) == (mergedB == null); 209 210 210 211 for (Route.Segment s : newRoad.getRoute().getSegments()) { 211 212 final Road oldRoad = roads.put(s.getWay(), newRoad); 212 213 213 214 if (oldRoad != null) { 214 215 if (mergedA == null) { … … 220 221 } 221 222 } 222 223 223 224 private void addRoad(Road newRoad) { 224 225 addRoad(newRoad, null, null); 225 226 } 226 227 227 228 private Road mergeRoads(Road a, Road b) { 228 229 final String ERR_ILLEGAL_ARGS = "The given roads can not be merged into one."; 229 230 230 231 final List<Way> ws = new ArrayList<>(CollectionUtils.toList(CollectionUtils.reverse(a.getRoute().getWays()))); 231 232 final List<Way> bws = b.getRoute().getWays(); 232 233 233 234 int i = -1; 234 235 for (Way w : ws) { … … 239 240 } 240 241 } 241 242 242 243 if (i < 0) { 243 244 throw new IllegalArgumentException(ERR_ILLEGAL_ARGS); 244 245 } 245 246 ws.addAll(bws.subList(i + 1, bws.size())); 246 247 247 248 final Route mergedRoute = Route.create(ws, a.getRoute().getLastSegment().getEnd()); 248 249 return new Road(this, mergedRoute); 249 250 } 250 251 251 252 private Junction register(Junction j) { 252 253 if (junctions.put(j.getNode(), j) != null) { 253 254 throw new IllegalStateException(); 254 255 } 255 256 256 257 return j; 257 258 } 258 259 259 260 public Set<Junction> getPrimaryJunctions() { 260 261 if (empty) { 261 262 return Collections.emptySet(); 262 263 } 263 264 264 265 final Set<Junction> pjs = new HashSet<>(); 265 266 for (Node n : primaryNodes) { … … 268 269 return pjs; 269 270 } 270 271 271 272 public Set<Road> getPrimaryRoads() { 272 273 if (empty) { 273 274 return Collections.emptySet(); 274 275 } 275 276 276 277 final Set<Road> prs = new HashSet<>(); 277 278 for (Way w : primaryWays) { … … 280 281 return prs; 281 282 } 282 283 283 284 public ModelContainer recalculate() { 284 285 return new ModelContainer(primaryNodes, primaryWays, false); 285 286 } 286 287 287 288 public boolean isPrimary(Junction j) { 288 289 return primaryNodes.contains(j.getNode()); 289 290 } 290 291 291 292 public boolean isPrimary(Road r) { 292 293 return primaryWays.contains(r.getRoute().getFirstSegment().getWay()); 293 294 } 294 295 295 296 public boolean isEmpty() { 296 297 return empty; 297 298 } 298 299 299 300 public boolean hasRoad(Way w) { 300 301 return roads.containsKey(w); 301 302 } 302 303 303 304 public boolean hasJunction(Node n) { 304 305 return junctions.containsKey(n); -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Road.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 23 24 24 25 public class Road { 25 public class End {26 public final class End { 26 27 private final boolean from; 27 28 private final Junction junction; 28 29 29 30 private final Relation lengthsLeft; 30 31 private final Relation lengthsRight; 31 32 32 33 private final double extraLengthLeft; 33 34 private final double extraLengthRight; 34 35 35 36 private final List<Lane> lanes; 36 37 private Set<Turn> turns; 37 38 38 39 private End(boolean from, Junction junction, Relation lengthsLeft, Relation lengthsRight) { 39 40 this.from = from; … … 44 45 this.extraLengthRight = lengthsRight == null ? 0 : Route.load(lengthsRight).getLengthFrom(getWay()); 45 46 this.lanes = Lane.load(this); 46 47 47 48 junction.addRoad(getWay()); 48 49 } 49 50 50 51 private End(boolean from, Junction junction) { 51 52 this.from = from; … … 56 57 this.extraLengthRight = 0; 57 58 this.lanes = Lane.load(this); 58 59 59 60 junction.addRoad(getWay()); 60 61 } 61 62 62 63 public Road getRoad() { 63 64 return Road.this; 64 65 } 65 66 66 67 public Way getWay() { 67 68 return isFromEnd() ? getRoute().getFirstSegment().getWay() : getRoute().getLastSegment().getWay(); 68 69 } 69 70 70 71 public Junction getJunction() { 71 72 return junction; 72 73 } 73 74 74 75 public boolean isFromEnd() { 75 76 return from; 76 77 } 77 78 78 79 public boolean isToEnd() { 79 80 return !isFromEnd(); 80 81 } 81 82 82 83 public End getOppositeEnd() { 83 84 return isFromEnd() ? toEnd : fromEnd; 84 85 } 85 86 86 87 /** 87 88 * @return the turns <em>onto</em> this road at this end … … 90 91 return turns; 91 92 } 92 93 93 94 public void addLane(Lane.Kind kind) { 94 95 if (kind == Lane.Kind.REGULAR) { 95 96 throw new IllegalArgumentException("Only extra lanes can be added."); 96 97 } 97 98 98 99 double length = Double.POSITIVE_INFINITY; 99 100 for (Lane l : lanes) { … … 102 103 } 103 104 } 104 105 105 106 if (Double.isInfinite(length)) { 106 107 length = Math.min(20, 3 * getLength() / 4); 107 108 } 108 109 109 110 addLane(kind, length); 110 111 } 111 112 112 113 private void addLane(Lane.Kind kind, double length) { 113 114 assert kind == Lane.Kind.EXTRA_LEFT || kind == Lane.Kind.EXTRA_RIGHT; 114 115 115 116 final GenericCommand cmd = new GenericCommand(getJunction().getNode().getDataSet(), "Add lane"); 116 117 117 118 final boolean left = kind == Lane.Kind.EXTRA_LEFT; 118 119 final Relation rel = left ? lengthsLeft : lengthsRight; 119 120 final Relation other = left ? lengthsRight : lengthsLeft; 120 121 final Node n = getJunction().getNode(); 121 122 122 123 final String lengthStr = toLengthString(length); 123 124 final Relation target; … … 131 132 target = rel; 132 133 } 133 134 134 135 final String key = left ? Constants.LENGTHS_KEY_LENGTHS_LEFT : Constants.LENGTHS_KEY_LENGTHS_RIGHT; 135 136 final String old = target.get(key); … … 139 140 cmd.backup(target).put(key, old + Constants.SEPARATOR + lengthStr); 140 141 } 141 142 142 143 Main.main.undoRedo.add(cmd); 143 144 } 144 145 145 146 private Relation createLengthsRelation() { 146 147 final Node n = getJunction().getNode(); 147 148 148 149 final Relation r = new Relation(); 149 150 r.put("type", Constants.TYPE_LENGTHS); 150 151 151 152 r.addMember(new RelationMember(Constants.LENGTHS_ROLE_END, n)); 152 153 for (Route.Segment s : isFromEnd() ? route.getSegments() : CollectionUtils.reverse(route.getSegments())) { 153 154 r.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, s.getWay())); 154 155 } 155 156 156 157 n.getDataSet().addPrimitive(r); 157 158 158 159 return r; 159 160 } 160 161 161 162 void updateLengths() { 162 163 final GenericCommand cmd = new GenericCommand(getJunction().getNode().getDataSet(), "Change lane length"); 163 164 164 165 for (final boolean left : Arrays.asList(true, false)) { 165 166 final Lane.Kind kind = left ? Lane.Kind.EXTRA_LEFT : Lane.Kind.EXTRA_RIGHT; 166 167 final Relation r = left ? lengthsLeft : lengthsRight; 167 168 final double extra = left ? extraLengthLeft : extraLengthRight; 168 169 169 170 if (r == null) { 170 171 continue; 171 172 } 172 173 173 174 final StringBuilder lengths = new StringBuilder(32); 174 175 for (Lane l : left ? CollectionUtils.reverse(lanes) : lanes) { … … 177 178 } 178 179 } 179 180 180 181 lengths.setLength(lengths.length() - Constants.SEPARATOR.length()); 181 182 cmd.backup(r).put(left ? Constants.LENGTHS_KEY_LENGTHS_LEFT : Constants.LENGTHS_KEY_LENGTHS_RIGHT, 182 183 lengths.toString()); 183 184 } 184 185 185 186 Main.main.undoRedo.add(cmd); 186 187 } 187 188 188 189 public List<Lane> getLanes() { 189 190 return lanes; 190 191 } 191 192 192 193 public Lane getLane(Lane.Kind kind, int index) { 193 194 for (Lane l : lanes) { … … 196 197 } 197 198 } 198 199 199 200 throw new IllegalArgumentException("No such lane."); 200 201 } 201 202 202 203 public Lane getExtraLane(int index) { 203 204 return index < 0 ? getLane(Lane.Kind.EXTRA_LEFT, index) : getLane(Lane.Kind.EXTRA_RIGHT, index); 204 205 } 205 206 206 207 public boolean isExtendable() { 207 208 final End o = getOppositeEnd(); 208 209 return (lengthsLeft == null && lengthsRight == null) && (o.lengthsLeft != null || o.lengthsRight != null); 209 210 } 210 211 211 212 public void extend(Way way) { 212 213 if (!isExtendable()) { 213 214 throw new IllegalStateException(); 214 215 } 215 216 216 217 final End o = getOppositeEnd(); 217 218 if (o.lengthsLeft != null) { … … 222 223 } 223 224 } 224 225 225 226 public List<Double> getLengths(Lane.Kind kind) { 226 227 switch (kind) { … … 233 234 } 234 235 } 235 236 236 237 void removeLane(GenericCommand cmd, Lane lane) { 237 238 assert lane.getKind() == Lane.Kind.EXTRA_LEFT || lane.getKind() == Lane.Kind.EXTRA_RIGHT; 238 239 239 240 final boolean left = lane.getKind() == Lane.Kind.EXTRA_LEFT; 240 241 final Relation rel = left ? lengthsLeft : lengthsRight; 241 242 242 243 for (Turn t : Turn.load(getContainer(), Constants.TURN_ROLE_FROM, getWay())) { 243 244 t.fixReferences(cmd, left, lane.getIndex()); 244 245 } 245 246 246 247 final double extraLength = left ? extraLengthLeft : extraLengthRight; 247 248 final List<Double> newLengths = new ArrayList<>(); … … 255 256 } 256 257 } 257 258 258 259 final AbstractPrimitive bRel = cmd.backup(rel); 259 260 bRel.put(key, join(newLengths)); 260 261 261 262 if (bRel.get(Constants.LENGTHS_KEY_LENGTHS_LEFT) == null 262 263 && bRel.get(Constants.LENGTHS_KEY_LENGTHS_RIGHT) == null) { … … 264 265 } 265 266 } 266 267 267 268 void initialize() { 268 269 this.turns = Collections.unmodifiableSet(Turn.load(getContainer(), Constants.TURN_ROLE_TO, getWay())); 269 270 270 271 for (Lane l : lanes) { 271 272 l.initialize(); … … 273 274 } 274 275 } 275 276 276 277 private static Pair<Relation, Relation> getLengthRelations(Way w, Node n) { 277 278 final List<Relation> left = new ArrayList<>(); 278 279 final List<Relation> right = new ArrayList<>(); 279 280 280 281 for (OsmPrimitive p : w.getReferrers()) { 281 282 if (p.getType() != OsmPrimitiveType.RELATION) { 282 283 continue; 283 284 } 284 285 285 286 Relation r = (Relation) p; 286 287 287 288 if (Constants.TYPE_LENGTHS.equals(r.get("type")) && isRightDirection(r, w, n)) { 288 289 289 290 if (r.get(Constants.LENGTHS_KEY_LENGTHS_LEFT) != null) { 290 291 left.add(r); 291 292 } 292 293 293 294 if (r.get(Constants.LENGTHS_KEY_LENGTHS_RIGHT) != null) { 294 295 right.add(r); … … 296 297 } 297 298 } 298 299 299 300 if (left.size() > 1) { 300 301 throw new IllegalArgumentException("Way is in " + left.size() 301 302 + " lengths relations for given direction, both specifying left lane lengths."); 302 303 } 303 304 304 305 if (right.size() > 1) { 305 306 throw new IllegalArgumentException("Way is in " + right.size() 306 307 + " lengths relations for given direction, both specifying right lane lengths."); 307 308 } 308 309 return new Pair<>( //310 left.isEmpty() ? null : left.get(0), //311 right.isEmpty() ? null : right.get(0) //309 310 return new Pair<>( 311 left.isEmpty() ? null : left.get(0), 312 right.isEmpty() ? null : right.get(0) 312 313 ); 313 314 } 314 315 315 316 /** 316 317 * @param r … … 328 329 } 329 330 } 330 331 331 332 return false; 332 333 } 333 334 334 335 private final ModelContainer container; 335 336 private final Route route; 336 337 private final End fromEnd; 337 338 private final End toEnd; 338 339 339 340 Road(ModelContainer container, Way w, Junction j) { 340 341 final Node n = j.getNode(); … … 343 344 } 344 345 final Pair<Relation, Relation> lengthsRelations = getLengthRelations(w, n); 345 346 346 347 this.container = container; 347 348 this.route = lengthsRelations.a == null && lengthsRelations.b == null ? Route.create(Arrays.asList(w), n) … … 350 351 this.toEnd = new End(false, j, lengthsRelations.a, lengthsRelations.b); 351 352 } 352 353 353 354 Road(ModelContainer container, Route route) { 354 355 this.container = container; … … 357 358 this.toEnd = new End(false, container.getJunction(route.getEnd())); 358 359 } 359 360 360 361 public End getFromEnd() { 361 362 return fromEnd; 362 363 } 363 364 364 365 public End getToEnd() { 365 366 return toEnd; 366 367 } 367 368 368 369 public Route getRoute() { 369 370 return route; 370 371 } 371 372 372 373 public double getLength() { 373 374 return route.getLength(); 374 375 } 375 376 376 377 private static String join(List<Double> list) { 377 378 if (list.isEmpty()) { 378 379 return null; 379 380 } 380 381 381 382 final StringBuilder builder = new StringBuilder(list.size() * (4 + Constants.SEPARATOR.length())); 382 383 383 384 for (double e : list) { 384 385 builder.append(toLengthString(e)).append(Constants.SEPARATOR); 385 386 } 386 387 387 388 builder.setLength(builder.length() - Constants.SEPARATOR.length()); 388 389 return builder.toString(); 389 390 } 390 391 391 392 private static String toLengthString(double length) { 392 393 final DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(); … … 396 397 return nf.format(length); 397 398 } 398 399 399 400 public ModelContainer getContainer() { 400 401 return container; 401 402 } 402 403 403 404 public boolean isPrimary() { 404 405 return getContainer().isPrimary(this); 405 406 } 406 407 407 408 void initialize() { 408 409 fromEnd.initialize(); -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Route.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 9 10 import org.openstreetmap.josm.data.osm.Way; 10 11 11 public class Route {12 public final class Route { 12 13 public static final class Segment { 13 14 private final Node start; 14 15 private final Way way; 15 16 private final Node end; 16 17 17 18 private final List<Node> nodes; 18 19 19 20 Segment(Node start, Way way, Node end) { 20 21 this.start = start; 21 22 this.way = way; 22 23 this.end = end; 23 24 24 25 final List<Node> ns = way.getNodes(); 25 26 if (way.lastNode().equals(start)) { 26 27 Collections.reverse(ns); 27 28 } 28 29 29 30 this.nodes = Collections.unmodifiableList(ns); 30 31 } 31 32 32 33 public Node getStart() { 33 34 return start; 34 35 } 35 36 36 37 public Way getWay() { 37 38 return way; 38 39 } 39 40 40 41 public Node getEnd() { 41 42 return end; 42 43 } 43 44 44 45 public List<Node> getNodes() { 45 46 return nodes; 46 47 } 47 48 48 49 public double getLength() { 49 50 double length = 0; 50 51 51 52 Node last = nodes.get(0); 52 53 for (Node n : nodes.subList(1, nodes.size())) { … … 54 55 last = n; 55 56 } 56 57 57 58 return length; 58 59 } 59 60 60 61 @Override 61 62 public int hashCode() { … … 67 68 return result; 68 69 } 69 70 70 71 @Override 71 72 public boolean equals(Object obj) { … … 95 96 } 96 97 } 97 98 98 99 public static Route load(Relation r) { 99 100 final Node end = Utils.getMemberNode(r, Constants.LENGTHS_ROLE_END); 100 101 final List<Way> ws = Utils.getMemberWays(r, Constants.LENGTHS_ROLE_WAYS); 101 102 102 103 return create(ws, end); 103 104 } 104 105 105 106 public static Route load(Relation left, Relation right, Way w) { 106 107 left = left == null ? right : left; 107 108 right = right == null ? left : right; 108 109 109 110 if (left == null) { 110 111 throw new IllegalArgumentException("At least one relation must not be null."); 111 112 } 112 113 113 114 final Route leftRoute = load(left); 114 115 final Route rightRoute = load(right); 115 116 116 117 int iLeft = 0; 117 118 while (!w.equals(leftRoute.getSegments().get(iLeft++).getWay())) 118 119 ; 119 120 120 121 int iRight = 0; 121 122 while (!w.equals(rightRoute.getSegments().get(iRight++).getWay())) 122 123 ; 123 124 124 125 final int min = Math.min(iLeft, iRight); 125 126 126 127 final List<Segment> leftSegments = leftRoute.getSegments().subList(iLeft - min, iLeft); 127 128 final List<Segment> rightSegments = rightRoute.getSegments().subList(iRight - min, iRight); 128 129 129 130 if (!leftSegments.equals(rightSegments)) { 130 131 throw new IllegalArgumentException("Routes are split across different ways."); 131 132 } 132 133 133 134 return new Route(iLeft == min ? rightSegments : leftSegments); 134 135 } 135 136 136 137 public static Route create(List<Way> ws, Node end) { 137 138 final List<Segment> segments = new ArrayList<>(ws.size()); 138 139 139 140 for (Way w : ws) { 140 141 if (!w.isFirstLastNode(end)) { 141 142 throw new IllegalArgumentException("Ways must be ordered."); 142 143 } 143 144 144 145 final Node start = Utils.getOppositeEnd(w, end); 145 146 segments.add(0, new Segment(start, w, end)); 146 147 end = start; 147 148 } 148 149 149 150 return new Route(segments); 150 151 } 151 152 152 153 private final List<Segment> segments; 153 154 154 155 private Route(List<Segment> segments) { 155 156 this.segments = Collections.unmodifiableList(new ArrayList<>(segments)); 156 157 } 157 158 158 159 public List<Segment> getSegments() { 159 160 return segments; 160 161 } 161 162 162 163 public List<Node> getNodes() { 163 164 final List<Node> ns = new ArrayList<>(); 164 165 165 166 ns.add(segments.get(0).getStart()); 166 167 for (Segment s : segments) { 167 168 ns.addAll(s.getNodes().subList(1, s.getNodes().size())); 168 169 } 169 170 170 171 return Collections.unmodifiableList(ns); 171 172 } 172 173 173 174 public double getLengthFrom(Way w) { 174 175 double length = Double.NEGATIVE_INFINITY; 175 176 176 177 for (Segment s : getSegments()) { 177 178 length += s.getLength(); 178 179 179 180 if (w.equals(s.getWay())) { 180 181 length = 0; 181 182 } 182 183 } 183 184 184 185 if (length < 0) { 185 186 throw new IllegalArgumentException("Way must be part of the route."); 186 187 } 187 188 188 189 return length; 189 190 } 190 191 191 192 public double getLength() { 192 193 double length = 0; 193 194 194 195 for (Segment s : getSegments()) { 195 196 length += s.getLength(); 196 197 } 197 198 198 199 return length; 199 200 } 200 201 201 202 public Node getStart() { 202 203 return getFirstSegment().getStart(); 203 204 } 204 205 205 206 public Node getEnd() { 206 207 return getLastSegment().getEnd(); 207 208 } 208 209 209 210 public Segment getFirstSegment() { 210 211 return getSegments().get(0); 211 212 } 212 213 213 214 public Segment getLastSegment() { 214 215 return getSegments().get(getSegments().size() - 1); 215 216 } 216 217 217 218 public Route subRoute(int fromIndex, int toIndex) { 218 219 return new Route(segments.subList(fromIndex, toIndex)); 219 220 } 220 221 221 222 public List<Way> getWays() { 222 223 final List<Way> ws = new ArrayList<>(); 223 224 224 225 for (Segment s : segments) { 225 226 ws.add(s.getWay()); 226 227 } 227 228 228 229 return Collections.unmodifiableList(ws); 229 230 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Turn.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 22 23 static Set<Turn> load(ModelContainer c, String role, OsmPrimitive primitive) { 23 24 final Set<Turn> result = new HashSet<>(); 24 25 25 26 for (Relation r : OsmPrimitive.getFilteredList(primitive.getReferrers(), Relation.class)) { 26 27 if (!r.isUsable() || !r.get("type").equals(Constants.TYPE_TURNS)) { 27 28 continue; 28 29 } 29 30 30 31 for (RelationMember m : r.getMembers()) { 31 32 if (m.getRole().equals(role) && m.getMember().equals(primitive)) { … … 34 35 } 35 36 } 36 37 return result; 38 } 39 37 38 return result; 39 } 40 40 41 static Set<Turn> load(ModelContainer c, Relation r) { 41 42 for (RelationMember m : r.getMembers()) { … … 48 49 } 49 50 } 50 51 51 52 throw new IllegalArgumentException("No via node or way(s)."); 52 53 } 53 54 54 55 private static Set<Turn> loadWithViaWays(ModelContainer c, Relation r) { 55 56 final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM); 56 57 final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO); 57 58 58 59 if (!c.hasRoad(from) || !c.hasRoad(to)) { 59 60 return Collections.emptySet(); 60 61 } 61 62 62 63 final List<Way> tmp = Utils.getMemberWays(r, Constants.TURN_ROLE_VIA); 63 64 final LinkedList<Road> via = new LinkedList<>(); 64 65 65 66 final Road.End fromRoadEnd = c.getJunction(Utils.lineUp(from, tmp.get(0))).getRoadEnd(from); 66 67 67 68 Node n = fromRoadEnd.getJunction().getNode(); 68 69 final Iterator<Way> it = tmp.iterator(); … … 72 73 return Collections.emptySet(); 73 74 } 74 75 75 76 final Road v = c.getRoad(w); 76 77 via.add(v); 77 78 n = Utils.getOppositeEnd(w, n); 78 79 79 80 if (!v.isPrimary()) { 80 81 throw new IllegalStateException("The road is not part of the junction."); 81 82 } 82 83 83 84 final Iterator<Route.Segment> it2 = (v.getRoute().getFirstSegment().getWay().equals(w) ? v.getRoute() 84 85 .getSegments() : CollectionUtils.reverse(v.getRoute().getSegments())).iterator(); 85 86 it2.next(); // first is done 86 87 87 88 while (it2.hasNext()) { 88 89 final Way w2 = it2.next().getWay(); 89 90 n = Utils.getOppositeEnd(w2, n); 90 91 91 92 if (!it.hasNext() || !w2.equals(it.next())) { 92 93 throw new IllegalStateException("The via ways of the relation do not form a road."); … … 96 97 final Road.End toRoadEnd = c.getJunction(n).getRoadEnd(to); 97 98 n = Utils.getOppositeEnd(to, n); 98 99 99 100 final Set<Turn> result = new HashSet<>(); 100 101 for (int i : indices(r, Constants.TURN_KEY_LANES)) { … … 106 107 return result; 107 108 } 108 109 109 110 static List<Integer> indices(Relation r, String key) { 110 111 final String joined = r.get(key); 111 112 112 113 if (joined == null) { 113 114 return new ArrayList<>(1); 114 115 } 115 116 116 117 final List<Integer> result = new ArrayList<>(); 117 118 for (String lane : Constants.SPLIT_PATTERN.split(joined)) { 118 119 result.add(Integer.parseInt(lane)); 119 120 } 120 121 return result; 122 } 123 121 122 return result; 123 } 124 124 125 private static Set<Turn> loadWithViaNode(ModelContainer c, Relation r) { 125 126 final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM); 126 127 final Node via = Utils.getMemberNode(r, Constants.TURN_ROLE_VIA); 127 128 final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO); 128 129 129 130 if (!c.hasRoad(from) || !c.hasJunction(via) || !c.hasRoad(to)) { 130 131 return Collections.emptySet(); 131 132 } 132 133 133 134 final Junction j = c.getJunction(via); 134 135 135 136 final Road.End fromRoadEnd = j.getRoadEnd(from); 136 137 final Road.End toRoadEnd = j.getRoadEnd(to); 137 138 138 139 final Set<Turn> result = new HashSet<>(); 139 140 for (int i : indices(r, Constants.TURN_KEY_LANES)) { 140 result.add(new Turn(r, fromRoadEnd.getLane(Lane.Kind.REGULAR, i), Collections.<Road> 141 result.add(new Turn(r, fromRoadEnd.getLane(Lane.Kind.REGULAR, i), Collections.<Road>emptyList(), toRoadEnd)); 141 142 } 142 143 for (int i : indices(r, Constants.TURN_KEY_EXTRA_LANES)) { 143 result.add(new Turn(r, fromRoadEnd.getExtraLane(i), Collections.<Road> 144 } 145 return result; 146 } 147 144 result.add(new Turn(r, fromRoadEnd.getExtraLane(i), Collections.<Road>emptyList(), toRoadEnd)); 145 } 146 return result; 147 } 148 148 149 static String join(List<Integer> list) { 149 150 if (list.isEmpty()) { 150 151 return null; 151 152 } 152 153 153 154 final StringBuilder builder = new StringBuilder(list.size() * (2 + Constants.SEPARATOR.length())); 154 155 155 156 for (int e : list) { 156 157 builder.append(e).append(Constants.SEPARATOR); 157 158 } 158 159 159 160 builder.setLength(builder.length() - Constants.SEPARATOR.length()); 160 161 return builder.toString(); 161 162 } 162 163 163 164 private final Relation relation; 164 165 165 166 private final Lane from; 166 167 private final List<Road> via; 167 168 private final Road.End to; 168 169 169 170 public Turn(Relation relation, Lane from, List<Road> via, Road.End to) { 170 171 this.relation = relation; … … 173 174 this.to = to; 174 175 } 175 176 176 177 public Lane getFrom() { 177 178 return from; 178 179 } 179 180 180 181 public List<Road> getVia() { 181 182 return via; 182 183 } 183 184 184 185 public Road.End getTo() { 185 186 return to; 186 187 } 187 188 188 189 Relation getRelation() { 189 190 return relation; 190 191 } 191 192 192 193 public void remove() { 193 194 final GenericCommand cmd = new GenericCommand(relation.getDataSet(), tr("Delete turn.")); 194 195 195 196 remove(cmd); 196 197 197 198 Main.main.undoRedo.add(cmd); 198 199 } 199 200 200 201 void remove(GenericCommand cmd) { 201 202 final List<Integer> lanes = indices(relation, Constants.TURN_KEY_LANES); 202 203 final List<Integer> extraLanes = indices(relation, Constants.TURN_KEY_EXTRA_LANES); 203 204 204 205 // TODO understand & document 205 206 if (lanes.size() + extraLanes.size() == 1 && (from.isExtra() ^ !lanes.isEmpty())) { … … 211 212 lanes.remove(Integer.valueOf(from.getIndex())); 212 213 } 213 214 214 215 cmd.backup(relation).put(Constants.TURN_KEY_LANES, lanes.isEmpty() ? null : join(lanes)); 215 216 cmd.backup(relation).put(Constants.TURN_KEY_EXTRA_LANES, extraLanes.isEmpty() ? null : join(extraLanes)); 216 217 } 217 218 218 219 void fixReferences(GenericCommand cmd, boolean left, int index) { 219 220 final List<Integer> fixed = new ArrayList<>(); … … 225 226 } 226 227 } 227 228 228 229 cmd.backup(relation).put(Constants.TURN_KEY_EXTRA_LANES, join(fixed)); 229 230 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/UnexpectedDataException.java
r26154 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 7 8 public final class UnexpectedDataException extends RuntimeException { 8 9 private static final long serialVersionUID = 7430280313889494242L; 9 10 10 11 public enum Kind { 11 12 NO_MEMBER("No member with role \"{0}\".", 1), … … 14 15 INVALID_TAG_FORMAT("The tag \"{0}\" has an invalid format: {1}", 2), 15 16 MISSING_TAG("The tag \"{0}\" is missing.", 1); 16 17 17 18 private final String message; 18 19 private final int params; 19 20 privateKind(String message, int params) {20 21 Kind(String message, int params) { 21 22 this.message = message; 22 23 this.params = params; 23 24 } 24 25 25 26 public UnexpectedDataException chuck(Object... args) { 26 27 throw new UnexpectedDataException(this, format(args)); 27 28 } 28 29 29 30 public String format(Object... args) { 30 31 if (args.length != params) { 31 32 throw new IllegalArgumentException("Wrong argument count for " + this + ": " + Arrays.toString(args)); 32 33 } 33 34 34 35 return tr(message, args); 35 36 } 36 37 } 37 38 38 39 private final Kind kind; 39 40 40 41 public UnexpectedDataException(Kind kind, String message) { 41 42 super(message); 42 43 43 44 this.kind = kind; 44 45 } 45 46 46 47 public Kind getKind() { 47 48 return kind; -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Utils.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 19 20 import org.openstreetmap.josm.plugins.turnlanes.CollectionUtils; 20 21 21 public class Utils {22 public final class Utils { 22 23 private static final Set<String> ROAD_HIGHWAY_VALUES = Collections.unmodifiableSet(new HashSet<>(Arrays 23 24 .asList("motorway", "motorway_link", "trunk", "trunk_link", "primary", "primary_link", "secondary", 24 25 "secondary_link", "tertiary", "tertiary_link", "residential", "unclassified", "road", "living_street", "service", 25 26 "track", "pedestrian", "raceway", "services"))); 26 27 28 private Utils() { 29 // Hide default constructor for utilities classes 30 } 31 27 32 public static boolean isRoad(Way w) { 28 33 return ROAD_HIGHWAY_VALUES.contains(w.get("highway")); 29 34 } 30 31 public static finalList<Way> filterRoads(List<OsmPrimitive> of) {35 36 public static List<Way> filterRoads(List<OsmPrimitive> of) { 32 37 final List<Way> result = new ArrayList<>(); 33 38 34 39 for (OsmPrimitive p : of) { 35 40 if (p.getType() == OsmPrimitiveType.WAY && Utils.isRoad((Way) p)) { … … 37 42 } 38 43 } 39 40 return result; 41 } 42 44 45 return result; 46 } 47 43 48 public static Node getMemberNode(Relation r, String role) { 44 49 return getMember(r, role, OsmPrimitiveType.NODE).getNode(); 45 50 } 46 51 47 52 public static Way getMemberWay(Relation r, String role) { 48 53 return getMember(r, role, OsmPrimitiveType.WAY).getWay(); 49 54 } 50 55 51 56 public static RelationMember getMember(Relation r, String role, OsmPrimitiveType type) { 52 57 final List<RelationMember> candidates = getMembers(r, role, type); … … 58 63 return candidates.get(0); 59 64 } 60 65 61 66 public static List<RelationMember> getMembers(Relation r, String role, OsmPrimitiveType type) { 62 67 final List<RelationMember> result = getMembers(r, role); … … 68 73 return result; 69 74 } 70 75 71 76 public static List<RelationMember> getMembers(Relation r, String role) { 72 77 final List<RelationMember> result = new ArrayList<>(); … … 78 83 return result; 79 84 } 80 85 81 86 public static List<Node> getMemberNodes(Relation r, String role) { 82 87 return mapMembers(getMembers(r, role, OsmPrimitiveType.NODE), Node.class); 83 88 } 84 89 85 90 public static List<Way> getMemberWays(Relation r, String role) { 86 91 return mapMembers(getMembers(r, role, OsmPrimitiveType.WAY), Way.class); 87 92 } 88 93 89 94 private static <T> List<T> mapMembers(List<RelationMember> ms, Class<T> t) { 90 95 final List<T> result = new ArrayList<>(ms.size()); … … 94 99 return result; 95 100 } 96 101 97 102 /** 98 * 99 * @param a 100 * @param b 103 * 104 * @param a first way 105 * @param b second way 101 106 * @return the node at which {@code a} and {@code b} are connected 102 107 */ … … 108 113 throw new IllegalArgumentException("Ways are not connected (at their first and last nodes)."); 109 114 } 110 115 111 116 if (a.firstNode() == b.firstNode() || a.lastNode() == b.firstNode()) { 112 117 return b.firstNode(); … … 117 122 } 118 123 } 119 124 120 125 public static Node getOppositeEnd(Way w, Node n) { 121 126 final boolean first = n.equals(w.firstNode()); 122 127 final boolean last = n.equals(w.lastNode()); 123 128 124 129 if (first && last) { 125 130 throw new IllegalArgumentException("Way starts as well as ends at the given node."); … … 132 137 } 133 138 } 134 139 135 140 /** 136 141 * Orders the {@code ways} such that the combined ways out of each returned list form a path (in 137 142 * order) from one node out of {@code nodes} to another out of {@code nodes}. 138 * 143 * 139 144 * <ul> 140 145 * <li>Each way is used exactly once.</li> … … 142 147 * <li>Paths contain no loops w.r.t. the ways' first and last nodes</li> 143 148 * </ul> 144 * 149 * 145 150 * @param ways 146 151 * ways to be ordered 147 152 * @param nodes 148 153 * start/end nodes 149 * @return 154 * @return ordered list 150 155 * @throws IllegalArgumentException 151 156 * if the ways can't be ordered … … 154 159 final List<Way> ws = new LinkedList<>(CollectionUtils.toList(ways)); 155 160 final Set<Node> ns = new HashSet<>(CollectionUtils.toList(nodes)); 156 161 157 162 final List<Route> result = new ArrayList<>(); 158 163 159 164 while (!ws.isEmpty()) { 160 165 result.add(findPath(ws, ns)); 161 166 } 162 163 return result; 164 } 165 167 168 return result; 169 } 170 166 171 private static Route findPath(List<Way> ws, Set<Node> ns) { 167 172 final Way w = findPathSegment(ws, ns); 168 173 final boolean first = ns.contains(w.firstNode()); 169 174 final boolean last = ns.contains(w.lastNode()); 170 175 171 176 if (first && last) { 172 177 return Route.create(Arrays.asList(w), w.firstNode()); … … 174 179 throw new AssertionError(); 175 180 } 176 181 177 182 final List<Way> result = new ArrayList<>(); 178 183 result.add(w); … … 182 187 result.add(next); 183 188 n = getOppositeEnd(next, n); 184 189 185 190 if (ns.contains(n)) { 186 191 return Route.create(result, first ? w.firstNode() : w.lastNode()); … … 188 193 } 189 194 } 190 195 191 196 private static Way findPathSegment(List<Way> ws, Collection<Node> ns) { 192 197 final Iterator<Way> it = ws.iterator(); 193 198 194 199 while (it.hasNext()) { 195 200 final Way w = it.next(); 196 201 197 202 if (ns.contains(w.firstNode()) || ns.contains(w.lastNode())) { 198 203 it.remove(); … … 200 205 } 201 206 } 202 207 203 208 throw new IllegalArgumentException("Ways can't be ordered."); 204 209 } 205 210 206 211 public static Iterable<Way> flattenVia(Node start, List<Road> via, Node end) { 207 212 final List<Way> result = new ArrayList<>(); 208 213 209 214 Node n = start; 210 215 for (Road r : via) { 211 216 final Iterable<Route.Segment> segments = r.getRoute().getFirstSegment().getWay().isFirstLastNode(n) ? r 212 217 .getRoute().getSegments() : CollectionUtils.reverse(r.getRoute().getSegments()); 213 218 214 219 for (Route.Segment s : segments) { 215 220 result.add(s.getWay()); … … 220 225 throw new IllegalArgumentException("The given via ways don't end at the given node."); 221 226 } 222 223 return result; 224 } 225 227 228 return result; 229 } 230 226 231 public static int parseIntTag(OsmPrimitive primitive, String tag) { 227 232 final String value = primitive.get(tag); 228 233 229 234 if (value != null) { 230 235 try { … … 234 239 } 235 240 } 236 241 237 242 throw UnexpectedDataException.Kind.MISSING_TAG.chuck(tag); 238 243 } -
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Validator.java
r30737 r33085 1 // License: GPL. For details, see LICENSE file. 1 2 package org.openstreetmap.josm.plugins.turnlanes.model; 2 3 … … 27 28 final Node junction; 28 29 final Way from; 29 30 publicKey(Node junction, Way from) {30 31 Key(Node junction, Way from) { 31 32 this.junction = junction; 32 33 this.from = from; 33 34 } 34 35 35 36 @Override 36 37 public int hashCode() { … … 41 42 return result; 42 43 } 43 44 44 45 @Override 45 46 public boolean equals(Object obj) { … … 64 65 } 65 66 } 66 67 67 68 final Key key; 68 69 private final int extraLeft; … … 70 71 private final int extraRight; 71 72 private final BitSet bitset; 72 73 publicIncomingLanes(Key key, int extraLeft, int regular, int extraRight) {73 74 IncomingLanes(Key key, int extraLeft, int regular, int extraRight) { 74 75 this.key = key; 75 76 this.extraLeft = extraLeft; … … 78 79 this.bitset = new BitSet(extraLeft + regular + extraRight); 79 80 } 80 81 81 82 public boolean existsRegular(int l) { 82 83 if (l > 0 && l <= regular) { … … 84 85 return true; 85 86 } 86 87 87 88 return false; 88 89 } 89 90 90 91 public boolean existsExtra(int l) { 91 92 if (l < 0 && Math.abs(l) <= extraLeft) { … … 98 99 return false; 99 100 } 100 101 101 102 public int unreferenced() { 102 103 return extraLeft + regular + extraRight - bitset.cardinality(); 103 104 } 104 105 } 105 106 106 107 public List<Issue> validate(DataSet dataSet) { 107 108 final List<Relation> lenghts = new ArrayList<>(); 108 109 final List<Relation> turns = new ArrayList<>(); 109 110 110 111 for (Relation r : OsmPrimitive.getFilteredList(dataSet.allPrimitives(), Relation.class)) { 111 112 if (!r.isUsable()) { 112 113 continue; 113 114 } 114 115 115 116 final String type = r.get("type"); 116 117 if (Constants.TYPE_LENGTHS.equals(type)) { … … 120 121 } 121 122 } 122 123 final List<Issue> issues = new ArrayList<>(); 124 123 124 final List<Issue> issues = new ArrayList<>(); 125 125 126 final Map<IncomingLanes.Key, IncomingLanes> incomingLanes = new HashMap<>(); 126 127 issues.addAll(validateLengths(lenghts, incomingLanes)); 127 128 issues.addAll(validateTurns(turns, incomingLanes)); 128 129 129 130 for (IncomingLanes lanes : incomingLanes.values()) { 130 131 if (lanes.unreferenced() > 0) { … … 133 134 } 134 135 } 135 136 136 137 return issues; 137 138 } 138 139 139 140 private List<Issue> validateLengths(List<Relation> lenghts, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) { 140 141 final List<Issue> issues = new ArrayList<>(); 141 142 142 143 for (Relation r : lenghts) { 143 144 issues.addAll(validateLengths(r, incomingLanes)); 144 145 } 145 146 146 147 return issues; 147 148 } 148 149 149 150 private List<Issue> validateLengths(Relation r, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) { 150 151 final List<Issue> issues = new ArrayList<>(); 151 152 152 153 try { 153 154 final Node end = Utils.getMemberNode(r, Constants.LENGTHS_ROLE_END); 154 155 final Route route = validateLengthsWays(r, end, issues); 155 156 156 157 if (route == null) { 157 158 return issues; 158 159 } 159 160 160 161 final List<Double> left = Lane.loadLengths(r, Constants.LENGTHS_KEY_LENGTHS_LEFT, 0); 161 162 final List<Double> right = Lane.loadLengths(r, Constants.LENGTHS_KEY_LENGTHS_RIGHT, 0); 162 163 163 164 int tooLong = 0; 164 165 for (Double l : left) { … … 172 173 } 173 174 } 174 175 175 176 if (tooLong > 0) { 176 177 issues.add(Issue.newError(r, end, "The lengths-relation specifies " + tooLong 177 178 + " extra-lanes which are longer than its ways.")); 178 179 } 179 180 180 181 putIncomingLanes(route, left, right, incomingLanes); 181 182 182 183 return issues; 183 184 184 185 } catch (UnexpectedDataException e) { 185 186 issues.add(Issue.newError(r, e.getMessage())); … … 187 188 } 188 189 } 189 190 190 191 private void putIncomingLanes(Route route, List<Double> left, List<Double> right, 191 192 Map<IncomingLanes.Key, IncomingLanes> incomingLanes) { 192 193 final Node end = route.getLastSegment().getEnd(); 193 194 final Way way = route.getLastSegment().getWay(); 194 195 195 196 final IncomingLanes.Key key = new IncomingLanes.Key(end, way); 196 197 final IncomingLanes lanes = new IncomingLanes(key, left.size(), Lane.getRegularCount(way, end), right.size()); 197 198 final IncomingLanes old = incomingLanes.put(key, lanes); 198 199 199 200 if (old != null) { 200 201 incomingLanes.put( … … 204 205 } 205 206 } 206 207 207 208 private Route validateLengthsWays(Relation r, Node end, List<Issue> issues) { 208 209 final List<Way> ways = Utils.getMemberWays(r, Constants.LENGTHS_ROLE_WAYS); 209 210 210 211 if (ways.isEmpty()) { 211 212 issues.add(Issue.newError(r, "A lengths-relation requires at least one member-way with role \"" … … 213 214 return null; 214 215 } 215 216 216 217 Node current = end; 217 218 for (Way w : ways) { … … 219 220 return orderWays(r, ways, current, issues, "ways", "lengths"); 220 221 } 221 222 222 223 current = Utils.getOppositeEnd(w, current); 223 224 } 224 225 225 226 return Route.create(ways, end); 226 227 } 227 228 228 229 private Route orderWays(final Relation r, List<Way> ways, Node end, List<Issue> issues, String role, String type) { 229 230 final List<Way> unordered = new ArrayList<>(ways); 230 231 final List<Way> ordered = new ArrayList<>(ways.size()); 231 232 final Set<Node> ends = new HashSet<>(); // to find cycles 232 233 233 234 Node current = end; 234 235 findNext: while (!unordered.isEmpty()) { … … 238 239 return null; 239 240 } 240 241 241 242 Iterator<Way> it = unordered.iterator(); 242 243 while (it.hasNext()) { 243 244 final Way w = it.next(); 244 245 245 246 if (w.isFirstLastNode(current)) { 246 247 it.remove(); … … 250 251 } 251 252 } 252 253 253 254 issues.add(Issue.newError(r, ways, "The " + role + " of the " + type + "-relation are disconnected.")); 254 255 return null; 255 256 } 256 257 257 258 final QuickFix quickFix = new QuickFix(tr("Put the ways in order.")) { 258 259 @Override … … 260 261 for (int i = r.getMembersCount() - 1; i >= 0; --i) { 261 262 final RelationMember m = r.getMember(i); 262 263 263 264 if (m.isWay() && Constants.LENGTHS_ROLE_WAYS.equals(m.getRole())) { 264 265 r.removeMember(i); 265 266 } 266 267 } 267 268 268 269 for (Way w : ordered) { 269 270 r.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, w)); 270 271 } 271 272 272 273 return true; 273 274 } 274 275 }; 275 276 276 277 issues.add(Issue.newError(r, ways, "The ways of the lengths-relation are unordered.", quickFix)); 277 278 278 279 return Route.create(ordered, end); 279 280 } 280 281 281 282 private List<Issue> validateTurns(List<Relation> turns, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) { 282 283 final List<Issue> issues = new ArrayList<>(); 283 284 284 285 for (Relation r : turns) { 285 286 issues.addAll(validateTurns(r, incomingLanes)); 286 287 } 287 288 288 289 return issues; 289 290 } 290 291 291 292 private List<Issue> validateTurns(Relation r, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) { 292 293 final List<Issue> issues = new ArrayList<>(); 293 294 294 295 try { 295 296 final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM); 296 297 final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO); 297 298 298 299 if (from.firstNode().equals(from.lastNode())) { 299 300 issues.add(Issue.newError(r, from, "The from-way both starts as well as ends at the via-node.")); … … 305 306 return issues; 306 307 } 307 308 308 309 final Node fromJunctionNode; 309 310 final List<RelationMember> viaMembers = Utils.getMembers(r, Constants.TURN_ROLE_VIA); … … 312 313 } else if (viaMembers.get(0).isWay()) { 313 314 final List<Way> vias = Utils.getMemberWays(r, Constants.TURN_ROLE_VIA); 314 315 315 316 fromJunctionNode = Utils.lineUp(from, vias.get(0)); 316 317 Node current = fromJunctionNode; … … 320 321 break; 321 322 } 322 323 323 324 current = Utils.getOppositeEnd(via, current); 324 325 } 325 326 } else { 326 327 final Node via = Utils.getMemberNode(r, Constants.TURN_ROLE_VIA); 327 328 328 329 if (!from.isFirstLastNode(via)) { 329 330 issues.add(Issue.newError(r, from, "The from-way does not start or end at the via-node.")); … … 332 333 issues.add(Issue.newError(r, to, "The to-way does not start or end at the via-node.")); 333 334 } 334 335 335 336 fromJunctionNode = via; 336 337 } 337 338 338 339 if (!issues.isEmpty()) { 339 340 return issues; 340 341 } 341 342 final IncomingLanes lanes = get(incomingLanes, fromJunctionNode, from); 342 343 343 344 for (int l : splitInts(r, Constants.TURN_KEY_LANES, issues)) { 344 345 if (!lanes.existsRegular(l)) { … … 346 347 } 347 348 } 348 349 349 350 for (int l : splitInts(r, Constants.TURN_KEY_EXTRA_LANES, issues)) { 350 351 if (!lanes.existsExtra(l)) { … … 352 353 } 353 354 } 354 355 355 356 return issues; 356 357 } catch (UnexpectedDataException e) { … … 359 360 } 360 361 } 361 362 362 363 private List<Integer> splitInts(Relation r, String key, List<Issue> issues) { 363 364 final String ints = r.get(key); 364 365 365 366 if (ints == null) { 366 367 return Collections.emptyList(); 367 368 } 368 369 369 370 final List<Integer> result = new ArrayList<>(); 370 371 371 372 for (String s : Constants.SPLIT_PATTERN.split(ints)) { 372 373 try { … … 377 378 } 378 379 } 379 380 380 381 return result; 381 382 } 382 383 383 384 private IncomingLanes get(Map<IncomingLanes.Key, IncomingLanes> incomingLanes, Node via, Way from) { 384 385 final IncomingLanes.Key key = new IncomingLanes.Key(via, from); 385 386 final IncomingLanes lanes = incomingLanes.get(key); 386 387 387 388 if (lanes == null) { 388 389 final IncomingLanes newLanes = new IncomingLanes(key, 0, Lane.getRegularCount(from, via), 0);
Note:
See TracChangeset
for help on using the changeset viewer.