1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean.twod.path;
18
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.Objects;
22
23 import org.apache.commons.geometry.euclidean.internal.AbstractPathConnector;
24 import org.apache.commons.geometry.euclidean.twod.LineConvexSubset;
25 import org.apache.commons.geometry.euclidean.twod.Vector2D;
26 import org.apache.commons.numbers.angle.Angle;
27
28
29
30
31 public abstract class AbstractLinePathConnector
32 extends AbstractPathConnector<AbstractLinePathConnector.ConnectableLineSubset> {
33
34
35
36
37
38
39 public void add(final LineConvexSubset subset) {
40 addPathElement(new ConnectableLineSubset(subset));
41 }
42
43
44
45
46
47
48
49
50
51 public void add(final Iterable<? extends LineConvexSubset> subsets) {
52 for (final LineConvexSubset subset : subsets) {
53 add(subset);
54 }
55 }
56
57
58
59
60
61
62
63
64
65
66
67 public void connect(final Iterable<? extends LineConvexSubset> subsets) {
68 final List<ConnectableLineSubset> newEntries = new ArrayList<>();
69
70 for (final LineConvexSubset subset : subsets) {
71 newEntries.add(new ConnectableLineSubset(subset));
72 }
73
74 connectPathElements(newEntries);
75 }
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 public List<LinePath> connectAll(final Iterable<LineConvexSubset> subsets) {
92 add(subsets);
93 return connectAll();
94 }
95
96
97
98
99
100
101
102
103 public List<LinePath> connectAll() {
104 final List<ConnectableLineSubset> roots = computePathRoots();
105 final List<LinePath> paths = new ArrayList<>(roots.size());
106
107 for (final ConnectableLineSubset root : roots) {
108 paths.add(toPath(root));
109 }
110
111 return paths;
112 }
113
114
115
116
117
118
119 private LinePath toPath(final ConnectableLineSubset root) {
120 final LinePath.Builder builder = LinePath.builder(null);
121
122 builder.append(root.getLineSubset());
123
124 ConnectableLineSubset current = root.getNext();
125
126 while (current != null && current != root) {
127 builder.append(current.getLineSubset());
128 current = current.getNext();
129 }
130
131 return builder.build();
132 }
133
134
135
136 protected static class ConnectableLineSubset
137 extends AbstractPathConnector.ConnectableElement<ConnectableLineSubset> {
138
139 private final Vector2D start;
140
141
142 private final LineConvexSubset subset;
143
144
145
146
147
148 public ConnectableLineSubset(final Vector2D start) {
149 this(start, null);
150 }
151
152
153
154
155 public ConnectableLineSubset(final LineConvexSubset subset) {
156 this(subset.getStartPoint(), subset);
157 }
158
159
160
161
162
163 private ConnectableLineSubset(final Vector2D start, final LineConvexSubset subset) {
164 this.start = start;
165 this.subset = subset;
166 }
167
168
169
170
171 public LineConvexSubset getLineSubset() {
172 return subset;
173 }
174
175
176 @Override
177 public boolean hasStart() {
178 return start != null;
179 }
180
181
182 @Override
183 public boolean hasEnd() {
184 return subset != null && subset.getEndPoint() != null;
185 }
186
187
188
189
190 public boolean hasZeroSize() {
191 return subset != null && subset.getPrecision().eqZero(subset.getSize());
192 }
193
194
195 @Override
196 public boolean endPointsEq(final ConnectableLineSubset other) {
197 if (hasEnd() && other.hasEnd()) {
198 return subset.getEndPoint()
199 .eq(other.subset.getEndPoint(), subset.getPrecision());
200 }
201
202 return false;
203 }
204
205
206 @Override
207 public boolean canConnectTo(final ConnectableLineSubset next) {
208 final Vector2D end = subset.getEndPoint();
209 final Vector2D nextStart = next.start;
210
211 return end != null && nextStart != null &&
212 end.eq(nextStart, subset.getPrecision());
213 }
214
215
216 @Override
217 public double getRelativeAngle(final ConnectableLineSubset next) {
218 return subset.getLine().angle(next.getLineSubset().getLine());
219 }
220
221
222 @Override
223 public ConnectableLineSubset getConnectionSearchKey() {
224 return new ConnectableLineSubset(subset.getEndPoint());
225 }
226
227
228 @Override
229 public boolean shouldContinueConnectionSearch(final ConnectableLineSubset candidate, final boolean ascending) {
230
231 if (candidate.hasStart()) {
232 final double candidateX = candidate.getLineSubset().getStartPoint().getX();
233 final double thisX = subset.getEndPoint().getX();
234 final int cmp = subset.getPrecision().compare(candidateX, thisX);
235
236 return ascending ? cmp <= 0 : cmp >= 0;
237 }
238
239 return true;
240 }
241
242
243 @Override
244 public int compareTo(final ConnectableLineSubset other) {
245
246 int cmp = Vector2D.COORDINATE_ASCENDING_ORDER.compare(start, other.start);
247 if (cmp == 0) {
248
249 final boolean thisHasSubset = subset != null;
250 final boolean otherHasSubset = other.subset != null;
251
252 cmp = Boolean.compare(thisHasSubset, otherHasSubset);
253
254 if (cmp == 0 && thisHasSubset) {
255
256 cmp = Boolean.compare(this.hasZeroSize(), other.hasZeroSize());
257
258 if (cmp == 0) {
259
260 final double aAngle = Angle.Rad.WITHIN_MINUS_PI_AND_PI.applyAsDouble(
261 this.getLineSubset().getLine().getAngle());
262 final double bAngle = Angle.Rad.WITHIN_MINUS_PI_AND_PI.applyAsDouble(
263 other.getLineSubset().getLine().getAngle());
264
265 cmp = Double.compare(aAngle, bAngle);
266 }
267 }
268 }
269 return cmp;
270 }
271
272
273 @Override
274 public int hashCode() {
275 return Objects.hash(start, subset);
276 }
277
278
279 @Override
280 public boolean equals(final Object obj) {
281 if (this == obj) {
282 return true;
283 }
284 if (obj == null || !this.getClass().equals(obj.getClass())) {
285 return false;
286 }
287
288 final ConnectableLineSubset other = (ConnectableLineSubset) obj;
289 return Objects.equals(this.start, other.start) &&
290 Objects.equals(this.subset, other.subset);
291 }
292
293
294 @Override
295 protected ConnectableLineSubset getSelf() {
296 return this;
297 }
298 }
299 }