1 package org.djutils.draw.line;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Iterator;
6 import java.util.List;
7
8 import org.djutils.draw.DrawRuntimeException;
9 import org.djutils.draw.point.Point3d;
10 import org.djutils.exceptions.Throw;
11
12 /**
13 * Polygon3d.java. Closed PolyLine3d. The actual closing point (which is the same as the starting point) is NOT included in the
14 * super PolyLine3d. The constructors automatically remove the last point if it is a at the same location as the first point.
15 * <p>
16 * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
17 * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
18 * </p>
19 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
20 * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
21 */
22 public class Polygon3d extends PolyLine3d
23 {
24 /** */
25 private static final long serialVersionUID = 20209999L;
26
27 /**
28 * Construct a new Polygon3d.
29 * @param x double[]; the x coordinates of the points
30 * @param y double[]; the y coordinates of the points
31 * @param z double[]; the z coordinates of the points
32 * @throws DrawRuntimeException when any two successive points are equal, or when there are too few points
33 */
34 public Polygon3d(final double[] x, final double[] y, final double[] z) throws DrawRuntimeException
35 {
36 super(fixClosingPointX(Throw.whenNull(x, "x may not be null"), Throw.whenNull(y, "y may not be null"),
37 Throw.whenNull(z, "z may not be null")), fixClosingPointY(x, y, z), fixClosingPointZ(x, y, z));
38 }
39
40 /**
41 * Ensure that the last point is not equal to the first. Remove the last point if necessary.
42 * @param x double[]; the x coordinates of the points
43 * @param y double[]; the y coordinates of the points
44 * @param z double[]; the z coordinates of the points
45 * @return double[]; the y coordinates of the points (possibly a copy with the last element removed)
46 */
47 static double[] fixClosingPointX(final double[] x, final double[] y, final double[] z)
48 {
49 if (x.length > 1 && y.length == x.length && z.length == x.length && x[0] == x[x.length - 1] && y[0] == y[x.length - 1]
50 && z[0] == z[z.length - 1])
51 {
52 return Arrays.copyOf(x, x.length - 1);
53 }
54 return x;
55 }
56
57 /**
58 * Ensure that the last point is not equal to the first. Remove the last point if necessary.
59 * @param x double[]; the x coordinates of the points
60 * @param y double[]; the y coordinates of the points
61 * @param z double[]; the z coordinates of the points
62 * @return double[]; the y coordinates of the points (possibly a copy with the last element removed)
63 */
64 static double[] fixClosingPointY(final double[] x, final double[] y, final double[] z)
65 {
66 if (x.length > 1 && y.length == x.length && z.length == x.length && x[0] == x[x.length - 1] && y[0] == y[x.length - 1]
67 && z[0] == z[z.length - 1])
68 {
69 return Arrays.copyOf(y, x.length - 1);
70 }
71 return y;
72 }
73
74 /**
75 * Ensure that the last point is not equal to the first. Remove the last point if necessary.
76 * @param x double[]; the x coordinates of the points
77 * @param y double[]; the y coordinates of the points
78 * @param z double[]; the z coordinates of the points
79 * @return double[]; the y coordinates of the points (possibly a copy with the last element removed)
80 */
81 static double[] fixClosingPointZ(final double[] x, final double[] y, final double[] z)
82 {
83 if (x.length > 1 && y.length == x.length && z.length == x.length && x[0] == x[x.length - 1] && y[0] == y[x.length - 1]
84 && z[0] == z[z.length - 1])
85 {
86 return Arrays.copyOf(z, x.length - 1);
87 }
88 return z;
89 }
90
91 /**
92 * Construct a new Polygon3d.
93 * @param points Point3d[]; array of Point3d objects.
94 * @throws NullPointerException when points is null
95 * @throws DrawRuntimeException when points is too short, or contains successive duplicate points
96 */
97 public Polygon3d(final Point3d[] points) throws NullPointerException, DrawRuntimeException
98 {
99 this(PolyLine3d.makeArray(Throw.whenNull(points, "points may not be null"), p -> p.x),
100 PolyLine3d.makeArray(points, p -> p.y), PolyLine3d.makeArray(points, p -> p.z));
101 }
102
103 /**
104 * Construct a new Polygon3d.
105 * @param point1 Point3d; the first point of the new Polygon3d
106 * @param point2 Point3d; the second point of the new Polygon3d
107 * @param otherPoints Point3d[]; all remaining points of the new Polygon3d (may be null)
108 * @throws NullPointerException when point1 or point2 is null
109 * @throws DrawRuntimeException when point1 is equal to the last point of otherPoints, or any two successive points are
110 * equal
111 */
112 public Polygon3d(final Point3d.html#Point3d">Point3d point1, final Point3d point2, final Point3d... otherPoints)
113 throws NullPointerException, DrawRuntimeException
114 {
115 super(Throw.whenNull(point1, "point1 may not be null"), Throw.whenNull(point2, "point2 may not be null"),
116 fixClosingPoint(point1, otherPoints));
117 }
118
119 /**
120 * Ensure that the last point of otherPoints is not equal to point1. Remove the last point if necessary.
121 * @param point1 Point3d; the first point of a new Polygon3d
122 * @param otherPoints Point3d[]; the remaining points of a new Polygon3d (may be null)
123 * @return Point3d[]; otherPoints (possibly a copy thereof with the last entry removed)
124 */
125 private static Point3d.html#Point3d">Point3dt3d">Point3d[] fixClosingPoint(final Point3d.html#Point3d">Point3d point1, final Point3d[] otherPoints)
126 {
127 if (otherPoints == null || otherPoints.length == 0)
128 {
129 return otherPoints;
130 }
131 Point3d[] result = otherPoints;
132 Point3d lastPoint = result[result.length - 1];
133 if (point1.x == lastPoint.x && point1.y == lastPoint.y)
134 {
135 result = Arrays.copyOf(otherPoints, result.length - 1);
136 lastPoint = result[result.length - 1];
137 }
138 Throw.when(point1.x == lastPoint.x && point1.y == lastPoint.y, DrawRuntimeException.class,
139 "Before last point and last point are at same location");
140 return result;
141 }
142
143 /**
144 * Construct a new Polygon3d from a list of Point3d objects.
145 * @param points List<Point3d>; the list of points
146 * @throws NullPointerException when points is null
147 * @throws DrawRuntimeException when points is too short, or the last two points are at the same location
148 */
149 public Polygon3d(final List<Point3d> points) throws NullPointerException, DrawRuntimeException
150 {
151 super(fixClosingPoint(true, Throw.whenNull(points, "points may not be null")));
152
153 }
154
155 /**
156 * Ensure that the last point in the list is different from the first point by possibly removing the last point.
157 * @param doNotModifyList boolean; if true; the list of points will not be modified (if the last point is to be removed; the
158 * entire list up to the last point is duplicated)
159 * @param points List<Point3d>; the list of points
160 * @return List<Point3d>; the fixed list
161 * @throws DrawRuntimeException when the (resulting) list is too short, or the before last and last point of points have the
162 * same coordinates
163 */
164 private static List<Point3d> fixClosingPoint(final boolean doNotModifyList, final List<Point3d> points)
165 throws DrawRuntimeException
166 {
167 Throw.when(points.size() < 2, DrawRuntimeException.class, "Need at least two points");
168 Point3d firstPoint = points.get(0);
169 Point3d lastPoint = points.get(points.size() - 1);
170 List<Point3d> result = points;
171 if (firstPoint.x == lastPoint.x && firstPoint.y == lastPoint.y && firstPoint.z == lastPoint.z)
172 {
173 if (doNotModifyList)
174 {
175 result = new ArrayList<>(points.size() - 1);
176 for (int i = 0; i < points.size() - 1; i++)
177 {
178 result.add(points.get(i));
179 }
180 }
181 else
182 {
183 result.remove(points.size() - 1);
184 }
185 lastPoint = result.get(result.size() - 1);
186 }
187 Throw.when(firstPoint.x == lastPoint.x && firstPoint.y == lastPoint.y && firstPoint.z == lastPoint.z,
188 DrawRuntimeException.class, "Before last point and last point are at same location");
189 return result;
190 }
191
192 /**
193 * Construct a new Polygon3d from an iterator that yields Point3d.
194 * @param iterator Iterator<Point3d>; the iterator
195 */
196 public Polygon3d(final Iterator<Point3d> iterator)
197 {
198 this(fixClosingPoint(false, iteratorToList(Throw.whenNull(iterator, "iterator cannot be null"))));
199 }
200
201 /**
202 * Create a new Polygon3d, optionally filtering out repeating successive points.
203 * @param filterDuplicates boolean; if true; filter out successive repeated points; otherwise do not filter
204 * @param points Point3d...; the coordinates of the polygon as Point3d
205 * @throws DrawRuntimeException when number of points < 2
206 */
207 public Polygon3d(final boolean filterDuplicates, final Point3d... points) throws DrawRuntimeException
208 {
209 this(PolyLine3d.cleanPoints(filterDuplicates, Arrays.stream(points).iterator()));
210 }
211
212 /**
213 * Create a new Polygon3d, optionally filtering out repeating successive points.
214 * @param filterDuplicates boolean; if true; filter out successive repeated points; otherwise do not filter
215 * @param pointList List<Point3d>; list of the coordinates of the line as Point3d; any duplicate points in this list
216 * are removed (this method may modify the provided list)
217 * @throws DrawRuntimeException when number of non-equal points < 2
218 */
219 public Polygon3d(final boolean filterDuplicates, final List<Point3d> pointList) throws DrawRuntimeException
220 {
221 this(PolyLine3d.cleanPoints(filterDuplicates, pointList.iterator()));
222 }
223
224 /** {@inheritDoc} */
225 @Override
226 public double getLength()
227 {
228 // Length a polygon is computed by taking the length of the PolyLine and adding the length of the closing segment
229 return super.getLength()
230 + Math.hypot(Math.hypot(getX(size() - 1) - getX(0), getY(size() - 1) - getY(0)), getZ(size() - 1) - getZ(0));
231 }
232
233 /** {@inheritDoc} */
234 @Override
235 public LineSegment3d getSegment(final int index)
236 {
237 if (index < size() - 1)
238 {
239 return super.getSegment(index);
240 }
241 Throw.when(index != size() - 1, DrawRuntimeException.class, "index must be in range 0..size() - 1");
242 return new LineSegment3d(getX(index), getY(index), getZ(index), getX(0), getY(0), getZ(0));
243 }
244
245 /** {@inheritDoc} */
246 @Override
247 public Polygon2d project() throws DrawRuntimeException
248 {
249 double[] projectedX = new double[this.size()];
250 double[] projectedY = new double[this.size()];
251
252 int nextIndex = 0;
253 for (int i = 0; i < this.size(); i++)
254 {
255 if (i > 0 && getX(i) == getX(i - 1) && getY(i) == getY(i - 1))
256 {
257 continue;
258 }
259 projectedX[nextIndex] = getX(i);
260 projectedY[nextIndex] = getY(i);
261 nextIndex++;
262 }
263 if (nextIndex < projectedX.length)
264 {
265 return new Polygon2d(Arrays.copyOf(projectedX, nextIndex), Arrays.copyOf(projectedY, nextIndex));
266 }
267 return new Polygon2d(projectedX, projectedY);
268 }
269
270 /** {@inheritDoc} */
271 @Override
272 public Polygon3d reverse()
273 {
274 return new Polygon3d(super.reverse().getPoints());
275 }
276
277 /** {@inheritDoc} */
278 @Override
279 public final String toExcel()
280 {
281 StringBuffer s = new StringBuffer();
282 for (int i = 0; i < size(); i++)
283 {
284 s.append(getX(i) + "\t" + getY(i) + "\t" + getZ(i) + "\n");
285 }
286 s.append(getX(0) + "\t" + getY(0) + "\t" + getZ(0) + "\n");
287 return s.toString();
288 }
289
290 /** {@inheritDoc} */
291 @Override
292 public final String toString()
293 {
294 return toString("%f", false);
295 }
296
297 /** {@inheritDoc} */
298 @Override
299 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
300 {
301 StringBuilder result = new StringBuilder();
302 if (!doNotIncludeClassName)
303 {
304 result.append("Polygon3d ");
305 }
306 result.append("[super=");
307 result.append(super.toString(doubleFormat, false));
308 result.append("]");
309 return result.toString();
310 }
311
312 }