1 package org.opentrafficsim.core.geometry;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.Locale;
6
7
8
9
10
11
12
13
14
15
16 public final class OTSOffsetLinePK
17 {
18
19 private OTSOffsetLinePK()
20 {
21
22 }
23
24
25 static boolean debugOffsetLine = false;
26
27
28 static double circlePrecision = 0.001;
29
30
31 static double offsetMinimumFilterValue = 0.001;
32
33
34 static double offsetMaximumFilterValue = 0.1;
35
36
37
38
39
40 static double offsetFilterRatio = 10;
41
42
43
44
45
46
47
48
49
50 public static OTSLine3D offsetLine(final OTSLine3D referenceLine, final double offset) throws OTSGeometryException
51 {
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 double bufferOffset = Math.abs(offset);
82 final double precision = 0.00001;
83 if (bufferOffset < precision)
84 {
85 return referenceLine;
86 }
87
88 OTSLine3D filteredReferenceLine =
89 referenceLine.noiseFilteredLine(Math.max(offsetMinimumFilterValue,
90 Math.min(bufferOffset / 10, offsetMaximumFilterValue)));
91 List<OTSPoint3D> tempPoints = new ArrayList<>();
92
93 OTSPoint3D prevPoint = filteredReferenceLine.get(0);
94 Double prevAngle = null;
95 for (int index = 0; index < filteredReferenceLine.size() - 1; index++)
96 {
97 OTSPoint3D nextPoint = filteredReferenceLine.get(index + 1);
98 double angle = Math.atan2(nextPoint.y - prevPoint.y, nextPoint.x - prevPoint.x);
99 if (debugOffsetLine)
100 {
101 System.out.println("#reference segment " + prevPoint + " to " + nextPoint + " angle " + Math.toDegrees(angle));
102 }
103 OTSPoint3D segmentFrom =
104 new OTSPoint3D(prevPoint.x - Math.sin(angle) * offset, prevPoint.y + Math.cos(angle) * offset);
105 OTSPoint3D segmentTo =
106 new OTSPoint3D(nextPoint.x - Math.sin(angle) * offset, nextPoint.y + Math.cos(angle) * offset);
107 boolean addSegment = true;
108 if (index > 0)
109 {
110 double deltaAngle = angle - prevAngle;
111 if (Math.abs(deltaAngle) > Math.PI)
112 {
113 deltaAngle -= Math.signum(deltaAngle) * 2 * Math.PI;
114 }
115 if (deltaAngle * offset <= 0)
116 {
117
118
119
120 int numSegments = 1;
121 if (Math.abs(deltaAngle) > Math.PI / 2)
122 {
123 numSegments = 2;
124 }
125 while (true)
126 {
127 double maxError = bufferOffset * (1 - Math.abs(Math.cos(deltaAngle / numSegments / 2)));
128 if (maxError < circlePrecision)
129 {
130 break;
131 }
132 numSegments *= 2;
133 }
134 OTSPoint3D prevArcPoint = tempPoints.get(tempPoints.size() - 1);
135
136 for (int additionalPoint = 1; additionalPoint < numSegments; additionalPoint++)
137 {
138 double intermediateAngle =
139 (additionalPoint * angle + (numSegments - additionalPoint) * prevAngle) / numSegments;
140 if (prevAngle * angle < 0 && Math.abs(prevAngle) > Math.PI / 2 && Math.abs(angle) > Math.PI / 2)
141 {
142 intermediateAngle += Math.PI;
143 }
144 OTSPoint3D intermediatePoint =
145 new OTSPoint3D(prevPoint.x - Math.sin(intermediateAngle) * offset, prevPoint.y
146 + Math.cos(intermediateAngle) * offset);
147
148 OTSPoint3D prevSegFrom = null;
149 int stopAt = tempPoints.size();
150 for (int i = 0; i < stopAt; i++)
151 {
152 OTSPoint3D prevSegTo = tempPoints.get(i);
153 if (null != prevSegFrom)
154 {
155 OTSPoint3D prevSegIntersection =
156 OTSPoint3D.intersectionOfLineSegments(prevArcPoint, intermediatePoint, prevSegFrom,
157 prevSegTo);
158 if (null != prevSegIntersection
159 && prevSegIntersection.horizontalDistanceSI(prevArcPoint) > circlePrecision
160 && prevSegIntersection.horizontalDistanceSI(prevSegFrom) > circlePrecision
161 && prevSegIntersection.horizontalDistanceSI(prevSegTo) > circlePrecision)
162 {
163 if (debugOffsetLine)
164 {
165 System.out.println("#inserting intersection in arc segment " + prevSegIntersection);
166 }
167 tempPoints.add(prevSegIntersection);
168 }
169 }
170 prevSegFrom = prevSegTo;
171 }
172 OTSPoint3D nextSegmentIntersection =
173 OTSPoint3D.intersectionOfLineSegments(prevSegFrom, intermediatePoint, segmentFrom, segmentTo);
174 if (null != nextSegmentIntersection)
175 {
176 if (debugOffsetLine)
177 {
178 System.out.println("#inserting intersection of arc segment with next segment "
179 + nextSegmentIntersection);
180 }
181 tempPoints.add(nextSegmentIntersection);
182 }
183 if (debugOffsetLine)
184 {
185 System.out.println("#inserting arc point " + intermediatePoint + " for angle "
186 + Math.toDegrees(intermediateAngle));
187 }
188 tempPoints.add(intermediatePoint);
189 prevArcPoint = intermediatePoint;
190 }
191 }
192
193
194 OTSPoint3D pPoint = null;
195 for (int i = 0; i < tempPoints.size(); i++)
196 {
197 OTSPoint3D p = tempPoints.get(i);
198 if (null != pPoint)
199 {
200 double pAngle = Math.atan2(p.y - pPoint.y, p.x - pPoint.x);
201 double angleDifference = angle - pAngle;
202 if (Math.abs(angleDifference) > Math.PI)
203 {
204 angleDifference += Math.signum(angleDifference) * 2 * Math.PI;
205 }
206 if (debugOffsetLine)
207 {
208 System.out.println("#preceding segment " + pPoint + " to " + p + ", next segment " + segmentFrom
209 + " to " + segmentTo + " angleDifference " + Math.toDegrees(angleDifference));
210 }
211 if (Math.abs(angleDifference) > 0)
212 {
213 OTSPoint3D intersection = OTSPoint3D.intersectionOfLineSegments(pPoint, p, segmentFrom, segmentTo);
214 if (null != intersection)
215 {
216 if (tempPoints.size() - 1 == i)
217 {
218 if (debugOffsetLine)
219 {
220 System.out.println("#Replacing last point of preceding segment and "
221 + "first point of next segment by their intersection " + intersection);
222 }
223 tempPoints.remove(tempPoints.size() - 1);
224 segmentFrom = intersection;
225 }
226 else
227 {
228 if (debugOffsetLine)
229 {
230 System.out.println("#Adding intersection of preceding segment and " + "next segment "
231 + intersection);
232 }
233 tempPoints.add(intersection);
234 }
235
236 }
237 }
238 else
239 {
240 if (debugOffsetLine)
241 {
242 System.out.println("#Not adding intersection of preceding segment and this segment "
243 + "(angle too small)");
244 }
245 if (i == tempPoints.size() - 1)
246 {
247 if (debugOffsetLine)
248 {
249 System.out.println("#Not adding segment");
250 }
251 addSegment = false;
252 }
253 }
254 }
255 pPoint = p;
256 }
257 }
258 if (addSegment)
259 {
260 if (debugOffsetLine)
261 {
262 System.out.println("#Adding segmentFrom " + segmentFrom);
263 }
264 tempPoints.add(segmentFrom);
265 if (debugOffsetLine)
266 {
267 System.out.println("#Adding segmentTo " + segmentTo);
268 }
269 tempPoints.add(segmentTo);
270 prevPoint = nextPoint;
271 prevAngle = angle;
272 }
273 }
274 if (debugOffsetLine)
275 {
276 System.out.println("# before cleanup: \nc1,0,0\n");
277 if (tempPoints.size() > 0)
278 {
279 OTSPoint3D p = tempPoints.get(0);
280 System.out.println(String.format(Locale.US, "M %.3f,%.3f", p.x, p.y));
281 for (int i = 1; i < tempPoints.size(); i++)
282 {
283 p = tempPoints.get(i);
284 System.out.println(String.format(Locale.US, "L %.3f,%.3f", p.x, p.y));
285 }
286 }
287 }
288
289 for (int index = 1; index < tempPoints.size() - 1; index++)
290 {
291 OTSPoint3D checkPoint = tempPoints.get(index);
292 prevPoint = null;
293 boolean tooClose = false;
294 boolean somewhereAtCorrectDistance = false;
295 for (int i = 0; i < filteredReferenceLine.size(); i++)
296 {
297 OTSPoint3D p = filteredReferenceLine.get(i);
298 if (null != prevPoint)
299 {
300 OTSPoint3D closestPoint = checkPoint.closestPointOnSegment(prevPoint, p);
301 double distance = closestPoint.horizontalDistanceSI(checkPoint);
302 if (distance < bufferOffset - circlePrecision)
303 {
304 if (debugOffsetLine)
305 {
306 System.out.print("#point " + checkPoint + " inside buffer (distance is " + distance + ") ");
307 }
308 tooClose = true;
309 break;
310 }
311 else if (distance < bufferOffset + precision)
312 {
313 somewhereAtCorrectDistance = true;
314 }
315 }
316 prevPoint = p;
317 }
318 if (tooClose || !somewhereAtCorrectDistance)
319 {
320 if (debugOffsetLine)
321 {
322 System.out.println("#Removing " + checkPoint);
323 }
324 tempPoints.remove(index);
325 index--;
326 }
327 }
328 if (debugOffsetLine)
329 {
330 System.out.println("#after cleanup " + tempPoints.size() + " points left");
331 }
332
333 for (int index = 0; index < tempPoints.size(); index++)
334 {
335 OTSPoint3D p = tempPoints.get(index);
336 if (Double.isNaN(p.z))
337 {
338 tempPoints.set(index, new OTSPoint3D(p.x, p.y, 0));
339 }
340 }
341 try
342 {
343 return OTSLine3D.createAndCleanOTSLine3D(tempPoints);
344 }
345 catch (OTSGeometryException exception)
346 {
347 exception.printStackTrace();
348 }
349 return null;
350 }
351 }