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

Last change on this file since 12966 was 12505, checked in by michael2402, 7 years ago

Add a method that allows traversing an offset version of a MapViewPath

File size: 6.5 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<MapViewPoint> 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 nodes The nodes of the original line
40 * @param offset The offset of the line.
41 */
42 public OffsetIterator(List<MapViewPoint> nodes, double offset) {
43 if (nodes.size() < 2) {
44 throw new IllegalArgumentException("There must be at least 2 nodes.");
45 }
46 this.mapState = nodes.get(0).getMapViewState();
47 this.nodes = nodes;
48 this.offset = offset;
49 }
50
51 /**
52 * Creates a new offset iterator
53 * @param mapState The map view state this iterator is for.
54 * @param nodes The nodes of the original line
55 * @param offset The offset of the line.
56 */
57 public OffsetIterator(MapViewState mapState, List<Node> nodes, double offset) {
58 this.mapState = mapState;
59 this.nodes = nodes.stream().filter(Node::isLatLonKnown).map(mapState::getPointFor).collect(Collectors.toList());
60 this.offset = offset;
61 }
62
63 @Override
64 public boolean hasNext() {
65 return idx < nodes.size();
66 }
67
68 @Override
69 public MapViewPoint next() {
70 if (!hasNext())
71 throw new NoSuchElementException();
72
73 MapViewPoint current = getForIndex(idx);
74
75 if (Math.abs(offset) < 0.1d) {
76 idx++;
77 return current;
78 }
79
80 double xCurrent = current.getInViewX();
81 double yCurrent = current.getInViewY();
82 if (idx == nodes.size() - 1) {
83 ++idx;
84 if (prev != null) {
85 return mapState.getForView(xPrev0 + xCurrent - prev.getInViewX(),
86 yPrev0 + yCurrent - prev.getInViewY());
87 } else {
88 return current;
89 }
90 }
91
92 MapViewPoint next = getForIndex(idx + 1);
93 double dxNext = next.getInViewX() - xCurrent;
94 double dyNext = next.getInViewY() - yCurrent;
95 double lenNext = Math.sqrt(dxNext*dxNext + dyNext*dyNext);
96
97 if (lenNext < 1e-11) {
98 lenNext = 1; // value does not matter, because dy_next and dx_next is 0
99 }
100
101 // calculate the position of the translated current point
102 double om = offset / lenNext;
103 double xCurrent0 = xCurrent + om * dyNext;
104 double yCurrent0 = yCurrent - om * dxNext;
105
106 if (idx == 0) {
107 ++idx;
108 prev = current;
109 xPrev0 = xCurrent0;
110 yPrev0 = yCurrent0;
111 return mapState.getForView(xCurrent0, yCurrent0);
112 } else {
113 double dxPrev = xCurrent - prev.getInViewX();
114 double dyPrev = yCurrent - prev.getInViewY();
115 // determine intersection of the lines parallel to the two segments
116 double det = dxNext*dyPrev - dxPrev*dyNext;
117 double m = dxNext*(yCurrent0 - yPrev0) - dyNext*(xCurrent0 - xPrev0);
118
119 if (Utils.equalsEpsilon(det, 0) || Math.signum(det) != Math.signum(m)) {
120 ++idx;
121 prev = current;
122 xPrev0 = xCurrent0;
123 yPrev0 = yCurrent0;
124 return mapState.getForView(xCurrent0, yCurrent0);
125 }
126
127 double f = m / det;
128 if (f < 0) {
129 ++idx;
130 prev = current;
131 xPrev0 = xCurrent0;
132 yPrev0 = yCurrent0;
133 return mapState.getForView(xCurrent0, yCurrent0);
134 }
135 // the position of the intersection or intermittent point
136 double cx = xPrev0 + f * dxPrev;
137 double cy = yPrev0 + f * dyPrev;
138
139 if (f > 1) {
140 // check if the intersection point is too far away, this will happen for sharp angles
141 double dxI = cx - xCurrent;
142 double dyI = cy - yCurrent;
143 double lenISq = dxI * dxI + dyI * dyI;
144
145 if (lenISq > Math.abs(2 * offset * offset)) {
146 // intersection point is too far away, calculate intermittent points for capping
147 double dxPrev0 = xCurrent0 - xPrev0;
148 double dyPrev0 = yCurrent0 - yPrev0;
149 double lenPrev0 = Math.sqrt(dxPrev0 * dxPrev0 + dyPrev0 * dyPrev0);
150 f = 1 + Math.abs(offset / lenPrev0);
151 double cxCap = xPrev0 + f * dxPrev;
152 double cyCap = yPrev0 + f * dyPrev;
153 xPrev0 = cxCap;
154 yPrev0 = cyCap;
155 // calculate a virtual prev point which lies on a line that goes through current and
156 // is perpendicular to the line that goes through current and the intersection
157 // so that the next capping point is calculated with it.
158 double lenI = Math.sqrt(lenISq);
159 double xv = xCurrent + dyI / lenI;
160 double yv = yCurrent - dxI / lenI;
161
162 prev = mapState.getForView(xv, yv);
163 return mapState.getForView(cxCap, cyCap);
164 }
165 }
166 ++idx;
167 prev = current;
168 xPrev0 = xCurrent0;
169 yPrev0 = yCurrent0;
170 return mapState.getForView(cx, cy);
171 }
172 }
173
174 private MapViewPoint getForIndex(int i) {
175 return nodes.get(i);
176 }
177
178 @Override
179 public void remove() {
180 throw new UnsupportedOperationException();
181 }
182}
Note: See TracBrowser for help on using the repository browser.