source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/OffsetIterator.java@ 11992

Last change on this file since 11992 was 11992, checked in by Don-vip, 7 years ago

fix #13665 - handle deleted nodes in way rendering

File size: 6.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm.visitor.paint;
3
4import java.util.Iterator;
5import java.util.List;
6import java.util.NoSuchElementException;
7import java.util.stream.Collectors;
8
9import org.openstreetmap.josm.data.osm.Node;
10import org.openstreetmap.josm.gui.MapViewState;
11import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
12import org.openstreetmap.josm.tools.Utils;
13
14/**
15 * Iterates over a list of Way Nodes and returns screen coordinates that
16 * represent a line that is shifted by a certain offset perpendicular
17 * to the way direction.
18 *
19 * There is no intention, to handle consecutive duplicate Nodes in a
20 * perfect way, but it should not throw an exception.
21 * @since 11696 made public
22 */
23public class OffsetIterator implements Iterator<MapViewPoint> {
24 private final MapViewState mapState;
25 private final List<Node> nodes;
26 private final double offset;
27 private int idx;
28
29 private MapViewPoint prev;
30 /* 'prev0' is a point that has distance 'offset' from 'prev' and the
31 * line from 'prev' to 'prev0' is perpendicular to the way segment from
32 * 'prev' to the current point.
33 */
34 private double xPrev0;
35 private double yPrev0;
36
37 /**
38 * Creates a new offset iterator
39 * @param mapState The map view state this iterator is for.
40 * @param nodes The nodes of the original line
41 * @param offset The offset of the line.
42 */
43 public OffsetIterator(MapViewState mapState, List<Node> nodes, double offset) {
44 this.mapState = mapState;
45 this.nodes = nodes.stream().filter(Node::isLatLonKnown).collect(Collectors.toList());
46 this.offset = offset;
47 idx = 0;
48 }
49
50 @Override
51 public boolean hasNext() {
52 return idx < nodes.size();
53 }
54
55 @Override
56 public MapViewPoint next() {
57 if (!hasNext())
58 throw new NoSuchElementException();
59
60 MapViewPoint current = getForIndex(idx);
61
62 if (Math.abs(offset) < 0.1d) {
63 idx++;
64 return current;
65 }
66
67 double xCurrent = current.getInViewX();
68 double yCurrent = current.getInViewY();
69 if (idx == nodes.size() - 1) {
70 ++idx;
71 if (prev != null) {
72 return mapState.getForView(xPrev0 + xCurrent - prev.getInViewX(),
73 yPrev0 + yCurrent - prev.getInViewY());
74 } else {
75 return current;
76 }
77 }
78
79 MapViewPoint next = getForIndex(idx + 1);
80 double dxNext = next.getInViewX() - xCurrent;
81 double dyNext = next.getInViewY() - yCurrent;
82 double lenNext = Math.sqrt(dxNext*dxNext + dyNext*dyNext);
83
84 if (lenNext < 1e-11) {
85 lenNext = 1; // value does not matter, because dy_next and dx_next is 0
86 }
87
88 // calculate the position of the translated current point
89 double om = offset / lenNext;
90 double xCurrent0 = xCurrent + om * dyNext;
91 double yCurrent0 = yCurrent - om * dxNext;
92
93 if (idx == 0) {
94 ++idx;
95 prev = current;
96 xPrev0 = xCurrent0;
97 yPrev0 = yCurrent0;
98 return mapState.getForView(xCurrent0, yCurrent0);
99 } else {
100 double dxPrev = xCurrent - prev.getInViewX();
101 double dyPrev = yCurrent - prev.getInViewY();
102 // determine intersection of the lines parallel to the two segments
103 double det = dxNext*dyPrev - dxPrev*dyNext;
104 double m = dxNext*(yCurrent0 - yPrev0) - dyNext*(xCurrent0 - xPrev0);
105
106 if (Utils.equalsEpsilon(det, 0) || Math.signum(det) != Math.signum(m)) {
107 ++idx;
108 prev = current;
109 xPrev0 = xCurrent0;
110 yPrev0 = yCurrent0;
111 return mapState.getForView(xCurrent0, yCurrent0);
112 }
113
114 double f = m / det;
115 if (f < 0) {
116 ++idx;
117 prev = current;
118 xPrev0 = xCurrent0;
119 yPrev0 = yCurrent0;
120 return mapState.getForView(xCurrent0, yCurrent0);
121 }
122 // the position of the intersection or intermittent point
123 double cx = xPrev0 + f * dxPrev;
124 double cy = yPrev0 + f * dyPrev;
125
126 if (f > 1) {
127 // check if the intersection point is too far away, this will happen for sharp angles
128 double dxI = cx - xCurrent;
129 double dyI = cy - yCurrent;
130 double lenISq = dxI * dxI + dyI * dyI;
131
132 if (lenISq > Math.abs(2 * offset * offset)) {
133 // intersection point is too far away, calculate intermittent points for capping
134 double dxPrev0 = xCurrent0 - xPrev0;
135 double dyPrev0 = yCurrent0 - yPrev0;
136 double lenPrev0 = Math.sqrt(dxPrev0 * dxPrev0 + dyPrev0 * dyPrev0);
137 f = 1 + Math.abs(offset / lenPrev0);
138 double cxCap = xPrev0 + f * dxPrev;
139 double cyCap = yPrev0 + f * dyPrev;
140 xPrev0 = cxCap;
141 yPrev0 = cyCap;
142 // calculate a virtual prev point which lies on a line that goes through current and
143 // is perpendicular to the line that goes through current and the intersection
144 // so that the next capping point is calculated with it.
145 double lenI = Math.sqrt(lenISq);
146 double xv = xCurrent + dyI / lenI;
147 double yv = yCurrent - dxI / lenI;
148
149 prev = mapState.getForView(xv, yv);
150 return mapState.getForView(cxCap, cyCap);
151 }
152 }
153 ++idx;
154 prev = current;
155 xPrev0 = xCurrent0;
156 yPrev0 = yCurrent0;
157 return mapState.getForView(cx, cy);
158 }
159 }
160
161 private MapViewPoint getForIndex(int i) {
162 return mapState.getPointFor(nodes.get(i));
163 }
164
165 @Override
166 public void remove() {
167 throw new UnsupportedOperationException();
168 }
169}
Note: See TracBrowser for help on using the repository browser.