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
86 List<OTSPoint3D> coordinates = new ArrayList<>(referenceLine.size());
87 for (int i = 0; i < referenceLine.size(); i++)
88 {
89 OTSPoint3D coordinate = referenceLine.get(i);
90 coordinates.add(new OTSPoint3D(coordinate.x, coordinate.y));
91 }
92 return OTSLine3D.createAndCleanOTSLine3D(coordinates);
93 }
94
95 OTSLine3D filteredReferenceLine =
96 referenceLine.noiseFilteredLine(Math.max(offsetMinimumFilterValue,
97 Math.min(bufferOffset / offsetFilterRatio, offsetMaximumFilterValue)));
98 List<OTSPoint3D> tempPoints = new ArrayList<>();
99
100 OTSPoint3D prevPoint = filteredReferenceLine.get(0);
101 Double prevAngle = null;
102 for (int index = 0; index < filteredReferenceLine.size() - 1; index++)
103 {
104 OTSPoint3D nextPoint = filteredReferenceLine.get(index + 1);
105 double angle = Math.atan2(nextPoint.y - prevPoint.y, nextPoint.x - prevPoint.x);
106 if (debugOffsetLine)
107 {
108 System.out.println("#reference segment " + prevPoint + " to " + nextPoint + " angle " + Math.toDegrees(angle));
109 }
110 OTSPoint3D segmentFrom =
111 new OTSPoint3D(prevPoint.x - Math.sin(angle) * offset, prevPoint.y + Math.cos(angle) * offset);
112 OTSPoint3D segmentTo =
113 new OTSPoint3D(nextPoint.x - Math.sin(angle) * offset, nextPoint.y + Math.cos(angle) * offset);
114 boolean addSegment = true;
115 if (index > 0)
116 {
117 double deltaAngle = angle - prevAngle;
118 if (Math.abs(deltaAngle) > Math.PI)
119 {
120 deltaAngle -= Math.signum(deltaAngle) * 2 * Math.PI;
121 }
122 if (deltaAngle * offset <= 0)
123 {
124
125
126
127 int numSegments = 1;
128 if (Math.abs(deltaAngle) > Math.PI / 2)
129 {
130 numSegments = 2;
131 }
132 while (true)
133 {
134 double maxError = bufferOffset * (1 - Math.abs(Math.cos(deltaAngle / numSegments / 2)));
135 if (maxError < circlePrecision)
136 {
137 break;
138 }
139 numSegments *= 2;
140 }
141 OTSPoint3D prevArcPoint = tempPoints.get(tempPoints.size() - 1);
142
143 for (int additionalPoint = 1; additionalPoint < numSegments; additionalPoint++)
144 {
145 double intermediateAngle =
146 (additionalPoint * angle + (numSegments - additionalPoint) * prevAngle) / numSegments;
147 if (prevAngle * angle < 0 && Math.abs(prevAngle) > Math.PI / 2 && Math.abs(angle) > Math.PI / 2)
148 {
149 intermediateAngle += Math.PI;
150 }
151 OTSPoint3D intermediatePoint =
152 new OTSPoint3D(prevPoint.x - Math.sin(intermediateAngle) * offset, prevPoint.y
153 + Math.cos(intermediateAngle) * offset);
154
155 OTSPoint3D prevSegFrom = null;
156 int stopAt = tempPoints.size();
157 for (int i = 0; i < stopAt; i++)
158 {
159 OTSPoint3D prevSegTo = tempPoints.get(i);
160 if (null != prevSegFrom)
161 {
162 OTSPoint3D prevSegIntersection =
163 OTSPoint3D.intersectionOfLineSegments(prevArcPoint, intermediatePoint, prevSegFrom,
164 prevSegTo);
165 if (null != prevSegIntersection
166 && prevSegIntersection.horizontalDistanceSI(prevArcPoint) > circlePrecision
167 && prevSegIntersection.horizontalDistanceSI(prevSegFrom) > circlePrecision
168 && prevSegIntersection.horizontalDistanceSI(prevSegTo) > circlePrecision)
169 {
170 if (debugOffsetLine)
171 {
172 System.out.println("#inserting intersection in arc segment " + prevSegIntersection);
173 }
174 tempPoints.add(prevSegIntersection);
175 }
176 }
177 prevSegFrom = prevSegTo;
178 }
179 OTSPoint3D nextSegmentIntersection =
180 OTSPoint3D.intersectionOfLineSegments(prevSegFrom, intermediatePoint, segmentFrom, segmentTo);
181 if (null != nextSegmentIntersection)
182 {
183 if (debugOffsetLine)
184 {
185 System.out.println("#inserting intersection of arc segment with next segment "
186 + nextSegmentIntersection);
187 }
188 tempPoints.add(nextSegmentIntersection);
189 }
190 if (debugOffsetLine)
191 {
192 System.out.println("#inserting arc point " + intermediatePoint + " for angle "
193 + Math.toDegrees(intermediateAngle));
194 }
195 tempPoints.add(intermediatePoint);
196 prevArcPoint = intermediatePoint;
197 }
198 }
199
200
201 OTSPoint3D pPoint = null;
202 for (int i = 0; i < tempPoints.size(); i++)
203 {
204 OTSPoint3D p = tempPoints.get(i);
205 if (null != pPoint)
206 {
207 double pAngle = Math.atan2(p.y - pPoint.y, p.x - pPoint.x);
208 double angleDifference = angle - pAngle;
209 if (Math.abs(angleDifference) > Math.PI)
210 {
211 angleDifference += Math.signum(angleDifference) * 2 * Math.PI;
212 }
213 if (debugOffsetLine)
214 {
215 System.out.println("#preceding segment " + pPoint + " to " + p + ", next segment " + segmentFrom
216 + " to " + segmentTo + " angleDifference " + Math.toDegrees(angleDifference));
217 }
218 if (Math.abs(angleDifference) > 0)
219 {
220 OTSPoint3D intersection = OTSPoint3D.intersectionOfLineSegments(pPoint, p, segmentFrom, segmentTo);
221 if (null != intersection)
222 {
223 if (tempPoints.size() - 1 == i)
224 {
225 if (debugOffsetLine)
226 {
227 System.out.println("#Replacing last point of preceding segment and "
228 + "first point of next segment by their intersection " + intersection);
229 }
230 tempPoints.remove(tempPoints.size() - 1);
231 segmentFrom = intersection;
232 }
233 else
234 {
235 if (debugOffsetLine)
236 {
237 System.out.println("#Adding intersection of preceding segment and " + "next segment "
238 + intersection);
239 }
240 tempPoints.add(intersection);
241 }
242
243 }
244 }
245 else
246 {
247 if (debugOffsetLine)
248 {
249 System.out.println("#Not adding intersection of preceding segment and this segment "
250 + "(angle too small)");
251 }
252 if (i == tempPoints.size() - 1)
253 {
254 if (debugOffsetLine)
255 {
256 System.out.println("#Not adding segment");
257 }
258 addSegment = false;
259 }
260 }
261 }
262 pPoint = p;
263 }
264 }
265 if (addSegment)
266 {
267 if (debugOffsetLine)
268 {
269 System.out.println("#Adding segmentFrom " + segmentFrom);
270 }
271 tempPoints.add(segmentFrom);
272 if (debugOffsetLine)
273 {
274 System.out.println("#Adding segmentTo " + segmentTo);
275 }
276 tempPoints.add(segmentTo);
277 prevPoint = nextPoint;
278 prevAngle = angle;
279 }
280 }
281 if (debugOffsetLine)
282 {
283 System.out.println("# before cleanup: \nc1,0,0\n");
284 if (tempPoints.size() > 0)
285 {
286 OTSPoint3D p = tempPoints.get(0);
287 System.out.println(String.format(Locale.US, "M %.3f,%.3f", p.x, p.y));
288 for (int i = 1; i < tempPoints.size(); i++)
289 {
290 p = tempPoints.get(i);
291 System.out.println(String.format(Locale.US, "L %.3f,%.3f", p.x, p.y));
292 }
293 }
294 }
295
296 for (int index = 1; index < tempPoints.size() - 1; index++)
297 {
298 OTSPoint3D checkPoint = tempPoints.get(index);
299 prevPoint = null;
300 boolean tooClose = false;
301 boolean somewhereAtCorrectDistance = false;
302 for (int i = 0; i < filteredReferenceLine.size(); i++)
303 {
304 OTSPoint3D p = filteredReferenceLine.get(i);
305 if (null != prevPoint)
306 {
307 OTSPoint3D closestPoint = checkPoint.closestPointOnSegment(prevPoint, p);
308 double distance = closestPoint.horizontalDistanceSI(checkPoint);
309 if (distance < bufferOffset - circlePrecision)
310 {
311 if (debugOffsetLine)
312 {
313 System.out.print("#point " + checkPoint + " inside buffer (distance is " + distance + ") ");
314 }
315 tooClose = true;
316 break;
317 }
318 else if (distance < bufferOffset + precision)
319 {
320 somewhereAtCorrectDistance = true;
321 }
322 }
323 prevPoint = p;
324 }
325 if (tooClose || !somewhereAtCorrectDistance)
326 {
327 if (debugOffsetLine)
328 {
329 System.out.println("#Removing " + checkPoint);
330 }
331 tempPoints.remove(index);
332 index--;
333 }
334 }
335 if (debugOffsetLine)
336 {
337 System.out.println("#after cleanup " + tempPoints.size() + " points left");
338 }
339
340 for (int index = 0; index < tempPoints.size(); index++)
341 {
342 OTSPoint3D p = tempPoints.get(index);
343 if (Double.isNaN(p.z))
344 {
345 tempPoints.set(index, new OTSPoint3D(p.x, p.y, 0));
346 }
347 }
348 try
349 {
350 return OTSLine3D.createAndCleanOTSLine3D(tempPoints);
351 }
352 catch (OTSGeometryException exception)
353 {
354 exception.printStackTrace();
355 }
356 return null;
357 }
358 }