1 package org.opentrafficsim.core.geometry;
2
3 import java.util.ArrayList;
4 import java.util.HashSet;
5 import java.util.List;
6 import java.util.Set;
7
8 import com.vividsolutions.jts.geom.Coordinate;
9 import com.vividsolutions.jts.geom.Geometry;
10 import com.vividsolutions.jts.linearref.LengthIndexedLine;
11 import com.vividsolutions.jts.operation.buffer.BufferParameters;
12
13
14
15
16
17
18
19
20
21
22
23 public final class OTSBuffering
24 {
25
26 private static final int QUADRANTSEGMENTS = 16;
27
28
29
30
31 private OTSBuffering()
32 {
33
34 }
35
36
37
38
39
40
41 private static double norm(final double angle)
42 {
43 double normalized = angle % (2 * Math.PI);
44 if (normalized < 0.0)
45 {
46 normalized += 2 * Math.PI;
47 }
48 return normalized;
49 }
50
51
52
53
54
55
56 private static double angle(final Coordinate c1, final Coordinate c2)
57 {
58 return norm(Math.atan2(c2.y - c1.y, c2.x - c1.x));
59 }
60
61
62
63
64
65
66
67
68 @SuppressWarnings("checkstyle:methodlength")
69 public static OTSLine3D offsetGeometry(final OTSLine3D referenceLine, final double offset) throws OTSGeometryException
70 {
71 Coordinate[] referenceCoordinates = referenceLine.getCoordinates();
72
73 double bufferOffset = Math.abs(offset);
74 final double precision = 0.00001;
75 if (bufferOffset < precision)
76 {
77
78 return new OTSLine3D(referenceCoordinates);
79 }
80 Geometry geometryLine = referenceLine.getLineString();
81 Coordinate[] bufferCoordinates =
82 geometryLine.buffer(bufferOffset, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
83
84
85 Coordinate sC0 = referenceCoordinates[0];
86 Coordinate sC1 = referenceCoordinates[1];
87 Coordinate eCm1 = referenceCoordinates[referenceCoordinates.length - 1];
88 Coordinate eCm2 = referenceCoordinates[referenceCoordinates.length - 2];
89 Set<Integer> startIndexSet = new HashSet<>();
90 Set<Coordinate> startSet = new HashSet<Coordinate>();
91 Set<Integer> endIndexSet = new HashSet<>();
92 Set<Coordinate> endSet = new HashSet<Coordinate>();
93 for (int i = 0; i < bufferCoordinates.length; i++)
94 {
95 Coordinate c = bufferCoordinates[i];
96 if (Math.abs(c.distance(sC0) - bufferOffset) < bufferOffset * precision && !startSet.contains(c))
97 {
98 startIndexSet.add(i);
99 startSet.add(c);
100 }
101 if (Math.abs(c.distance(eCm1) - bufferOffset) < bufferOffset * precision && !endSet.contains(c))
102 {
103 endIndexSet.add(i);
104 endSet.add(c);
105 }
106 }
107 if (startIndexSet.size() != 2)
108 {
109 throw new OTSGeometryException("offsetGeometry: startIndexSet.size() = " + startIndexSet.size());
110 }
111 if (endIndexSet.size() != 2)
112 {
113 throw new OTSGeometryException("offsetGeometry: endIndexSet.size() = " + endIndexSet.size());
114 }
115
116
117 int startIndex = -1;
118 int endIndex = -1;
119 double expectedStartAngle = norm(angle(sC0, sC1) + Math.signum(offset) * Math.PI / 2.0);
120 double expectedEndAngle = norm(angle(eCm2, eCm1) + Math.signum(offset) * Math.PI / 2.0);
121 for (int ic : startIndexSet)
122 {
123 if (norm(expectedStartAngle - angle(sC0, bufferCoordinates[ic])) < Math.PI / 4.0
124 || norm(angle(sC0, bufferCoordinates[ic]) - expectedStartAngle) < Math.PI / 4.0)
125 {
126 startIndex = ic;
127 }
128 }
129 for (int ic : endIndexSet)
130 {
131 if (norm(expectedEndAngle - angle(eCm1, bufferCoordinates[ic])) < Math.PI / 4.0
132 || norm(angle(eCm1, bufferCoordinates[ic]) - expectedEndAngle) < Math.PI / 4.0)
133 {
134 endIndex = ic;
135 }
136 }
137 if (startIndex == -1 || endIndex == -1)
138 {
139 throw new OTSGeometryException("offsetGeometry: could not find startIndex or endIndex");
140 }
141 startIndexSet.remove(startIndex);
142 endIndexSet.remove(endIndex);
143
144
145 List<Coordinate> coordinateList1 = new ArrayList<>();
146 List<Coordinate> coordinateList2 = new ArrayList<>();
147 boolean use1 = true;
148 boolean use2 = true;
149
150 int i = startIndex;
151 while (i != endIndex)
152 {
153 if (!coordinateList1.contains(bufferCoordinates[i]))
154 {
155 coordinateList1.add(bufferCoordinates[i]);
156 }
157 i = (i + 1) % bufferCoordinates.length;
158 if (startIndexSet.contains(i) || endIndexSet.contains(i))
159 {
160 use1 = false;
161 }
162 }
163 if (!coordinateList1.contains(bufferCoordinates[endIndex]))
164 {
165 coordinateList1.add(bufferCoordinates[endIndex]);
166 }
167
168 i = startIndex;
169 while (i != endIndex)
170 {
171 if (!coordinateList2.contains(bufferCoordinates[i]))
172 {
173 coordinateList2.add(bufferCoordinates[i]);
174 }
175 i = (i == 0) ? bufferCoordinates.length - 1 : i - 1;
176 if (startIndexSet.contains(i) || endIndexSet.contains(i))
177 {
178 use2 = false;
179 }
180 }
181 if (!coordinateList2.contains(bufferCoordinates[endIndex]))
182 {
183 coordinateList2.add(bufferCoordinates[endIndex]);
184 }
185
186 if (!use1 && !use2)
187 {
188 throw new OTSGeometryException("offsetGeometry: could not find path from start to end for offset");
189 }
190 if (use1 && use2)
191 {
192 throw new OTSGeometryException("offsetGeometry: Both paths from start to end for offset were found to be ok");
193 }
194 Coordinate[] coordinates;
195 if (use1)
196 {
197 coordinates = new Coordinate[coordinateList1.size()];
198 coordinateList1.toArray(coordinates);
199 }
200 else
201 {
202 coordinates = new Coordinate[coordinateList2.size()];
203 coordinateList2.toArray(coordinates);
204 }
205 return new OTSLine3D(coordinates);
206 }
207
208
209
210
211
212
213
214
215
216
217 public static OTSLine3D offsetLine(final OTSLine3D referenceLine, final double offsetAtStart, final double offsetAtEnd)
218 throws OTSGeometryException
219 {
220 OTSLine3D offsetLineAtStart = offsetGeometry(referenceLine, offsetAtStart);
221 if (offsetAtStart == offsetAtEnd)
222 {
223 return offsetLineAtStart;
224 }
225 OTSLine3D offsetLineAtEnd = offsetGeometry(referenceLine, offsetAtEnd);
226 Geometry startGeometry = offsetLineAtStart.getLineString();
227 Geometry endGeometry = offsetLineAtEnd.getLineString();
228 LengthIndexedLine first = new LengthIndexedLine(startGeometry);
229 double firstLength = startGeometry.getLength();
230 LengthIndexedLine second = new LengthIndexedLine(endGeometry);
231 double secondLength = endGeometry.getLength();
232 ArrayList<Coordinate> out = new ArrayList<Coordinate>();
233 Coordinate[] firstCoordinates = startGeometry.getCoordinates();
234 Coordinate[] secondCoordinates = endGeometry.getCoordinates();
235 int firstIndex = 0;
236 int secondIndex = 0;
237 Coordinate prevCoordinate = null;
238 final double tooClose = 0.05;
239 while (firstIndex < firstCoordinates.length && secondIndex < secondCoordinates.length)
240 {
241 double firstRatio =
242 firstIndex < firstCoordinates.length ? first.indexOf(firstCoordinates[firstIndex]) / firstLength
243 : Double.MAX_VALUE;
244 double secondRatio =
245 secondIndex < secondCoordinates.length ? second.indexOf(secondCoordinates[secondIndex]) / secondLength
246 : Double.MAX_VALUE;
247 double ratio;
248 if (firstRatio < secondRatio)
249 {
250 ratio = firstRatio;
251 firstIndex++;
252 }
253 else
254 {
255 ratio = secondRatio;
256 secondIndex++;
257 }
258 Coordinate firstCoordinate = first.extractPoint(ratio * firstLength);
259 Coordinate secondCoordinate = second.extractPoint(ratio * secondLength);
260 Coordinate resultCoordinate =
261 new Coordinate((1 - ratio) * firstCoordinate.x + ratio * secondCoordinate.x, (1 - ratio) * firstCoordinate.y
262 + ratio * secondCoordinate.y);
263 if (null == prevCoordinate || resultCoordinate.distance(prevCoordinate) > tooClose)
264 {
265 out.add(resultCoordinate);
266 prevCoordinate = resultCoordinate;
267 }
268 }
269 Coordinate[] resultCoordinates = new Coordinate[out.size()];
270 for (int index = 0; index < out.size(); index++)
271 {
272 resultCoordinates[index] = out.get(index);
273 }
274 return new OTSLine3D(resultCoordinates);
275 }
276 }