001/** 002 * PointString -- A collection of GeomPoint objects that make up a "string of points". 003 * 004 * Copyright (C) 2003-2025, by Joseph A. Huwaldt. All rights reserved. 005 * 006 * This library is free software; you can redistribute it and/or modify it under the terms 007 * of the GNU Lesser General Public License as published by the Free Software Foundation; 008 * either version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public License along with 015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - 016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html 017 */ 018package geomss.geom; 019 020import jahuwaldt.js.param.Parameter; 021import java.io.Serializable; 022import java.text.MessageFormat; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.Comparator; 026import java.util.Iterator; 027import static java.util.Objects.requireNonNull; 028import javax.measure.converter.ConversionException; 029import javax.measure.quantity.Dimensionless; 030import javax.measure.quantity.Length; 031import javax.measure.unit.NonSI; 032import javax.measure.unit.SI; 033import javax.measure.unit.Unit; 034import javolution.context.ObjectFactory; 035import javolution.context.StackContext; 036import javolution.util.FastTable; 037import javolution.xml.XMLFormat; 038import javolution.xml.stream.XMLStreamException; 039 040/** 041 * A <code>PointString</code> is a collection of {@link GeomPoint} objects that make up a 042 * "string of points". Any number of points may be added to a string, but all points in a 043 * string must have the same dimensions. 044 * <p> 045 * WARNING: This list allows geometry to be stored in different units. If consistent units 046 * are required, then the user must specifically convert the list items. 047 * </p> 048 * <p> Modified by: Joseph A. Huwaldt </p> 049 * 050 * @author Joseph A. Huwaldt, Date: March 5, 2003 051 * @version February 22, 2025 052 * 053 * @param <E> The type of GeomPoint contained in this list of points. 054 */ 055@SuppressWarnings({"serial", "CloneableImplementsClone"}) 056public final class PointString<E extends GeomPoint> extends AbstractPointGeomList<PointString<E>,E> { 057 058 private FastTable<E> _list; 059 060 /** 061 * Return the list underlying this geometry list. 062 * 063 * @return The list underlying this geometry list. 064 */ 065 @Override 066 protected FastTable<E> getList() { 067 return _list; 068 } 069 070 /** 071 * Returns a new, preallocated or recycled <code>PointString</code> instance 072 * (on the stack when executing in a <code>StackContext</code>), that can 073 * store a list of {@link GeomPoint} objects. 074 * 075 * @return A new PointString instance. 076 */ 077 public static PointString newInstance() { 078 PointString list = FACTORY.object(); 079 list._list = FastTable.newInstance(); 080 return list; 081 } 082 083 /** 084 * Returns a new, preallocated or recycled <code>PointString</code> instance 085 * (on the stack when executing in a <code>StackContext</code>) with the 086 * specified name, that can store a list of {@link GeomPoint} objects. 087 * 088 * @param name The name to be assigned to this list (may be <code>null</code>). 089 * @return A new PointString instance with the specified name. 090 */ 091 public static PointString newInstance(String name) { 092 PointString list = PointString.newInstance(); 093 list.setName(name); 094 return list; 095 } 096 097 /** 098 * Return a PointString made up of the {@link GeomPoint} objects in the 099 * specified collection. 100 * 101 * @param <E> The type of GeomPoint contained in this list of points. 102 * @param name The name to be assigned to this list (may be <code>null</code>). 103 * @param elements A collection of points. May not be null. 104 * @return A new PointString instance made up of the specified points. 105 */ 106 public static <E extends GeomPoint> PointString<E> valueOf(String name, Collection<E> elements) { 107 for (Object element : elements) { 108 requireNonNull(element, RESOURCES.getString("collectionElementsNullErr")); 109 if (!(element instanceof GeomPoint)) 110 throw new ClassCastException(MessageFormat.format( 111 RESOURCES.getString("listElementTypeErr"), "PointString", "GeomPoint")); 112 } 113 114 PointString<E> list = PointString.newInstance(name); 115 list.addAll(elements); 116 117 return list; 118 } 119 120 /** 121 * Return a PointString made up of the {@link GeomPoint} objects in the specified 122 * array. 123 * 124 * @param <E> The type of GeomPoint contained in this list of points. 125 * @param name The name to be assigned to this list (may be <code>null</code>). 126 * @param elements An array of points. May not be null. 127 * @return A new PointString instance made up of the specified points. 128 */ 129 public static <E extends GeomPoint> PointString<E> valueOf(String name, E... elements) { 130 requireNonNull(elements); 131 PointString<E> list = PointString.newInstance(name); 132 list.addAll(elements); 133 134 return list; 135 } 136 137 /** 138 * Return a PointString made up of the {@link GeomPoint} objects in the 139 * specified array. 140 * 141 * @param <E> The type of GeomPoint contained in this list of points. 142 * @param elements An array of points. May not be null. 143 * @return A new PointString instance made up of the specified points. 144 */ 145 public static <E extends GeomPoint> PointString<E> valueOf(E... elements) { 146 return PointString.valueOf(null, elements); 147 } 148 149 /** 150 * Return the total number of points in this geometry element. 151 * 152 * @return The total number of points in this geometry element. 153 */ 154 @Override 155 public int getNumberOfPoints() { 156 return size(); 157 } 158 159 /** 160 * Returns the range of elements in this list from the specified start and 161 * ending indexes. 162 * 163 * @param first index of the first element to return (0 returns the 1st 164 * element, -1 returns the last, etc). 165 * @param last index of the last element to return (0 returns the 1st 166 * element, -1 returns the last, etc). 167 * @return the list of elements in the given range from this list. 168 * @throws IndexOutOfBoundsException if the given index is out of range: 169 * <code>index ≥ size()</code> 170 */ 171 @Override 172 public PointString<E> getRange(int first, int last) { 173 first = normalizeIndex(first); 174 last = normalizeIndex(last); 175 176 PointString<E> list = PointString.newInstance(); 177 for (int i=first; i <= last; ++i) 178 list.add(get(i)); 179 180 return list; 181 } 182 183 /** 184 * Returns an new {@link PointString} with the elements in this list in 185 * reverse order. 186 * 187 * @return A new PointString with the elements in this string in reverse order. 188 */ 189 @Override 190 public PointString<E> reverse() { 191 PointString<E> list = PointString.newInstance(); 192 copyState(list); 193 int size = this.size(); 194 for (int i=size-1; i >= 0; --i) { 195 list.add(get(i)); 196 } 197 return list; 198 } 199 200 /** 201 * Returns a new {@link PointString} that is identical to this string but 202 * with every other point removed. The 1st point (index = 0) and last point 203 * (index = size()-1) are always retained. If there are less than 3 points 204 * in the string, then a new string is returned that contains the same 205 * points as this string. 206 * 207 * @return A new PointString identical to this one but with every other 208 * point removed. 209 */ 210 public PointString<E> thin() { 211 PointString<E> list = PointString.newInstance(); 212 copyState(list); 213 214 int size = size(); 215 if (size < 3) { 216 list.addAll(this); 217 return list; 218 } 219 220 int sizem1 = size - 1; 221 for (int i=0; i < sizem1; i += 2) { 222 list.add(get(i)); 223 } 224 list.add(get(size-1)); 225 226 return list; 227 } 228 229 /** 230 * Returns a new {@link PointString} that is identical to this string but 231 * with a new point linearly interpolated half-way between each of the 232 * existing points. If there are less than 2 points in the string, then a 233 * new string is returned that contains the same point as this string. 234 * 235 * @return A new PointString that is identical to this string but with a new 236 * point linearly interpolated half-way between each of the existing points. 237 */ 238 public PointString enrich() { 239 PointString list = PointString.newInstance(); 240 copyState(list); 241 242 int size = size(); 243 if (size < 2) { 244 list.add(get(0)); 245 return list; 246 } 247 248 GeomPoint p1 = get(0); 249 list.add(p1); 250 for (int i=1; i < size; ++i) { 251 GeomPoint p2 = get(i); 252 Point pa = p1.plus(p2).divide(2); // Calculate the average point. 253 list.add(pa); 254 list.add(p2); 255 p1 = p2; 256 } 257 258 return list; 259 } 260 261 /** 262 * Returns a new PointString that contains the points in this PointString with all 263 * duplicate points removed. 264 * 265 * @param tol The tolerance for determining if the points in this PointString 266 * are approx. equal. May not be null. 267 * @return A new PointString that contains the points in this PointString with all 268 * duplicate points removed. 269 */ 270 public PointString unique(Parameter<Length> tol) { 271 requireNonNull(tol); 272 PointString<GeomPoint> output = PointString.newInstance(); 273 copyState(output); 274 output.addAll(_list); 275 for (int i = 0; i < output.size(); i++) { 276 for (int j = i + 1; j < output.size(); j++) { 277 if (output.get(i).isApproxEqual(output.get(j), tol)) { 278 output.remove(j); 279 --j; 280 } 281 } 282 } 283 return output; 284 } 285 286 /** 287 * Returns the length of this PointString in terms of the sum of the distances between 288 * each consecutive point in the string. 289 * 290 * @return The length of the string: sum(point(i).distance(point(i-1))). 291 */ 292 public Parameter<Length> length() { 293 StackContext.enter(); 294 try { 295 int numPts = size(); 296 Parameter d = Parameter.ZERO_LENGTH.to(get(0).getUnit()); 297 for (int i = 1; i < numPts; ++i) { 298 Parameter<Length> dist = get(i).distance(get(i - 1)); 299 if (!dist.isApproxZero()) 300 d = d.plus(dist); 301 } 302 return StackContext.outerCopy(d); 303 } finally { 304 StackContext.exit(); 305 } 306 } 307 308 /** 309 * Calculate the average or centroid of all the points in this PointString. 310 * 311 * @return The average or centroid of all the points in this PointString. 312 */ 313 public Point getAverage() { 314 return GeomUtil.averagePoints(this); 315 } 316 317 /** 318 * Return <code>true</code> if this string of points is degenerate (i.e.: has length 319 * less than the specified tolerance). 320 * 321 * @param tol The tolerance for determining if this string of points is degenerate. 322 * May not be null. 323 * @return <code>true</code> if this string of points is degenerate 324 */ 325 public boolean isDegenerate(Parameter<Length> tol) { 326 requireNonNull(tol); 327 Parameter<Length> cLength = length(); 328 return cLength.compareTo(tol) <= 0; 329 } 330 331 /** 332 * Returns <code>true</code> if this string of points is a line to within the 333 * specified tolerance. 334 * 335 * @param tol The tolerance for determining if this string of points is a line. 336 * @return <code>true</code> if this string of points is a line 337 */ 338 public boolean isLine(Parameter<Length> tol) { 339 // Reference: Piegl, L.A., Tiller, W., "Computing Offsets of NURBS Curves and Surfaces", 340 // Computer Aided Design 31, 1999, pgs. 147-156. 341 requireNonNull(tol); 342 343 StackContext.enter(); 344 try { 345 // Extract the string end points. 346 GeomPoint p0 = get(0), p1 = get(-1); 347 348 // If the end points are coincident, then it can't be a line. 349 Vector<Length> tv = p1.minus(p0).toGeomVector(); 350 if (!tv.mag().isGreaterThan(tol)) 351 return false; 352 353 // If there are only two non-coincident points, then it must be a line. 354 if (size() == 2) 355 return true; 356 357 // Get a unit vector for the potential line. 358 Vector<Dimensionless> uv = tv.toUnitVector(); 359 360 // Loop over all the points in the string. 361 Parameter c2 = tv.dot(tv); 362 int numPnts = size() - 1; 363 for (int i = 1; i < numPnts; ++i) { 364 GeomPoint pi = get(i); 365 366 // Check distance of this point from infinite line p0-uv. 367 if (GeomUtil.pointLineDistance(pi, p0, uv).isGreaterThan(tol)) 368 return false; 369 370 // See if the point falls in the segment p0 to p1. 371 Vector<Length> w = pi.minus(p0).toGeomVector(); 372 Parameter c1 = w.dot(tv); 373 if (c1.getValue() < 0) 374 return false; 375 if (c2.isLessThan(c1)) 376 return false; 377 } 378 379 // Must be a straight line. 380 return true; 381 382 } finally { 383 StackContext.exit(); 384 } 385 } 386 387 /** 388 * Generic/default tolerance for use in root finders, etc. 389 */ 390 public static final double GTOL = 1e-6; 391 392 /** 393 * Return <code>true</code> if this string of points is planar or <code>false</code> 394 * if it is not. 395 * 396 * @param tol The geometric tolerance to use in determining if the string of points is 397 * planar. 398 * @return <code>true</code> if this string of points is planar 399 */ 400 public boolean isPlanar(Parameter<Length> tol) { 401 // If the string is less than 3D, then it must be planar. 402 int numDims = getPhyDimension(); 403 if (numDims <= 2) 404 return true; 405 406 // If there are 3 or fewer points, then it must be planar. 407 int numPnts = size(); 408 if (numPnts < 3) 409 return true; 410 411 StackContext.enter(); 412 try { 413 // Is this string of points degenerate? 414 if (isDegenerate(tol)) 415 return true; 416 417 // Is the string of points linear? 418 if (isLine(tol)) 419 return true; 420 421 // Extract 3 points from the string of points and form a plane form them. 422 GeomPoint p0 = get(0); 423 int idx1 = numPnts / 3; 424 if (idx1 == 0) 425 ++idx1; 426 int idx2 = 2 * numPnts / 3; 427 if (idx2 == idx1) 428 ++idx2; 429 GeomPoint p1 = get(idx1); 430 GeomPoint p2 = get(idx2); 431 Vector<Length> v1 = p1.minus(p0).toGeomVector(); 432 Vector<Length> v2 = p2.minus(p0).toGeomVector(); 433 Vector n = v1.cross(v2); 434 Vector<Dimensionless> nhat; 435 if (n.magValue() > GTOL) { 436 nhat = n.toUnitVector(); 437 nhat.setOrigin(Point.newInstance(getPhyDimension())); 438 439 } else { 440 // Try a different set of points on the string. 441 idx1 = numPnts / 4; 442 if (idx1 == 0) 443 ++idx1; 444 idx2 = 3 * numPnts / 4; 445 if (idx2 == idx1) 446 ++idx2; 447 p1 = get(idx1); 448 p2 = get(idx2); 449 v1 = p1.minus(p0).toGeomVector(); 450 v2 = p2.minus(p0).toGeomVector(); 451 n = v1.cross(v2); 452 if (n.magValue() > GTOL) { 453 nhat = n.toUnitVector(); 454 nhat.setOrigin(Point.newInstance(getPhyDimension())); 455 } else { 456 // Points from input curve likely form a straight line. 457 nhat = GeomUtil.calcYHat(p1.minus(p2).toGeomVector().toUnitVector()); 458 nhat = v2.cross(nhat).toUnitVector(); 459 } 460 } 461 462 // Loop over all the points in the point string 463 // (except 1st, which was used to define the plane). 464 for (int i = 1; i < numPnts; ++i) { 465 GeomPoint pi = get(i); 466 467 // Find deviation from a plane for each point by projecting a vector from p0 to pi 468 // onto the normal for the arbitrarily chosen points on the curve. 469 // If the projection is anything other than zero, the points are not 470 // in the same plane. 471 Vector<Length> p1pi = pi.minus(p0).toGeomVector(); 472 if (!p1pi.mag().isApproxZero()) { 473 Parameter proj = p1pi.dot(nhat); 474 if (proj.isLargerThan(tol)) 475 return false; 476 } 477 } 478 } finally { 479 StackContext.exit(); 480 } 481 482 return true; 483 } 484 485 /** 486 * Return a new {@link PointString} with the points in this list sorted into ascending 487 * order with respect to the specified coordinate dimension. This can also be used to 488 * remove duplicate points after the list has been sorted. To sort in descending 489 * order, use this method followed immediately by reverse() (e.g.: 490 * <code>str.sort(Point.X,false,null).reverse();</code>). 491 * 492 * @param dim The physical coordinate dimension to be sorted (e.g.: 0=X, 1=Y, etc). 493 * @param removeDup Duplicate points are removed from the output if this is true. 494 * @param tol The tolerance for identifying duplicate points. If null is passed, 495 * then essentially exact equality is required (if removeDup is 496 * false, you may pass null). 497 * @return A new PointString with the points in this list sorted into ascending order 498 * with respect to the specified coordinate dimension and possibly with 499 * duplicate points removed. 500 */ 501 public PointString<E> sort(int dim, boolean removeDup, Parameter<Length> tol) { 502 // Create a new PointString and add the points to it. 503 PointString<E> str = PointString.newInstance(); 504 str.addAll(this); 505 506 // Sort the new PointString 507 Collections.sort(str, new PntComparator(dim)); 508 509 // Remove duplicates if requested. 510 if (removeDup) { 511 E old = null; 512 Iterator<E> it = str.iterator(); 513 while (it.hasNext()) { 514 E p = it.next(); 515 if (p.isApproxEqual(old, tol)) 516 it.remove(); 517 old = p; 518 } 519 } 520 521 return str; 522 } 523 524 /** 525 * A Comparator that compares the specified dimension of two points for order. 526 */ 527 private class PntComparator implements Comparator<GeomPoint>, Serializable { 528 private final int _dim; 529 530 public PntComparator(int dim) { 531 _dim = dim; 532 } 533 534 @Override 535 public int compare(GeomPoint o1, GeomPoint o2) { 536 double v1 = o1.getValue(_dim); 537 double v2 = o2.getValue(_dim, o1.getUnit()); 538 return Double.compare(v1, v2); 539 } 540 } 541 542 /** 543 * Return the equivalent of this list converted to the specified number 544 * of physical dimensions. If the number of dimensions is greater than 545 * this element, then zeros are added to the additional dimensions. 546 * If the number of dimensions is less than this element, then 547 * the extra dimensions are simply dropped (truncated). If 548 * the new dimensions are the same as the dimension of this element, 549 * then this list is simply returned. 550 * 551 * @param newDim The dimension of the element to return. 552 * @return The equivalent of this list converted to the new dimensions. 553 */ 554 @Override 555 public PointString toDimension(int newDim) { 556 if (getPhyDimension() == newDim) 557 return this; 558 PointString newList = PointString.newInstance(); 559 copyState(newList); 560 int size = this.size(); 561 for (int i=0; i < size; ++i) { 562 E element = this.get(i); 563 newList.add(element.toDimension(newDim)); 564 } 565 return newList; 566 } 567 568 /** 569 * Returns the equivalent to this list but with <I>all</I> the elements stated in the 570 * specified unit. 571 * 572 * @param unit the length unit of the list to be returned. May not be null. 573 * @return an equivalent to this list but stated in the specified unit. 574 * @throws ConversionException if the the input unit is not a length unit. 575 */ 576 @Override 577 public PointString to(Unit<Length> unit) { 578 requireNonNull(unit); 579 PointString list = PointString.newInstance(); 580 copyState(list); 581 int size = this.size(); 582 for (int i = 0; i < size; ++i) { 583 E e = this.get(i); 584 list.add(e.to(unit)); 585 } 586 return list; 587 } 588 589 /** 590 * Returns a copy of this <code>PointString</code> instance 591 * {@link javolution.context.AllocatorContext allocated} by the calling 592 * thread (possibly on the stack). 593 * 594 * @return an identical and independent copy of this object. 595 */ 596 @Override 597 public PointString<E> copy() { 598 return copyOf(this); 599 } 600 601 /** 602 * Return a copy of this object with any transformations or subranges 603 * removed (applied). 604 * 605 * @return A copy of this object with any transformations or subranges 606 * removed (applied). 607 */ 608 @Override 609 public PointString<E> copyToReal() { 610 PointString<E> newList = PointString.newInstance(); 611 copyState(newList); 612 int size = this.size(); 613 for (int i=0; i < size; ++i) { 614 E element = this.get(i); 615 newList.add((E)element.copyToReal()); 616 } 617 return newList; 618 } 619 620 /** 621 * Returns transformed version of this element. The returned object 622 * implements {@link GeomTransform} and contains transformed versions of the 623 * contents of this list as children. 624 * 625 * @param transform The transformation to apply to this geometry. May not be null. 626 * @return A new PointString that is identical to this one with the 627 * specified transformation applied. 628 * @throws DimensionException if this element is not 3D. 629 */ 630 @Override 631 public PointString getTransformed(GTransform transform) { 632 requireNonNull(transform); 633 PointString list = PointString.newInstance(); 634 copyState(list); 635 int size = this.size(); 636 for (int i=0; i < size; ++i) { 637 E element = this.get(i); 638 list.add(element.getTransformed(transform)); 639 } 640 return list; 641 } 642 643 /** 644 * Replaces the {@link GeomPoint} at the specified position in this list with the 645 * specified element. Null elements are ignored. The input element must have the same 646 * physical dimensions as the other items in this list, or an exception is thrown. 647 * 648 * @param index The index of the element to replace (0 returns the 1st element, -1 649 * returns the last, -2 returns the 2nd from last, etc). 650 * @param element The element to be stored at the specified position. May not be null. 651 * @return The element previously at the specified position in this list. 652 * @throws java.lang.IndexOutOfBoundsException - if <code>index > size()</code> 653 * @throws DimensionException if the input element's dimensions are different from 654 * this list's dimensions. 655 */ 656 @Override 657 public E set(int index, E element) { 658 return super.set(index, requireNonNull(element)); 659 } 660 661 /** 662 * Inserts the specified {@link GeomPoint} at the specified position in this list. 663 * Shifts the element currently at that position (if any) and any subsequent elements 664 * to the right (adds one to their indices). Null values are ignored. The input 665 * value must have the same physical dimensions as the other items in this list, or 666 * an exception is thrown. 667 * <p> 668 * Note: If this method is used concurrent access must be synchronized (the list is 669 * not thread-safe). 670 * </p> 671 * 672 * @param index the index at which the specified element is to be inserted. (0 returns 673 * the 1st element, -1 returns the last, -2 returns the 2nd from last, 674 * etc). 675 * @param value the element to be inserted. May not be null. 676 * @throws IndexOutOfBoundsException if <code>index > size()</code> 677 * @throws DimensionException if the input value dimensions are different from 678 * this list's dimensions. 679 */ 680 @Override 681 public void add(int index, E value) { 682 super.add(index, requireNonNull(value)); 683 } 684 685 /** 686 * Inserts all of the {@link GeomPoint} objects in the specified collection into this 687 * list at the specified position. Shifts the element currently at that position (if 688 * any) and any subsequent elements to the right (increases their indices). The new 689 * elements will appear in this list in the order that they are returned by the 690 * specified collection's iterator. The behavior of this operation is unspecified if 691 * the specified collection is modified while the operation is in progress. Note that 692 * this will occur if the specified collection is this list, and it's nonempty. The 693 * input elements must have the same physical dimensions as the other items in this 694 * list, or an exception is thrown. 695 * 696 * @param index index at which to insert first element from the specified collection. 697 * @param c Elements to be inserted into this collection. May not be null. 698 * @return <code>true</code> if this collection changed as a result of the call 699 * @throws DimensionException if the input element's dimensions are different from 700 * this list's dimensions. 701 */ 702 @Override 703 public boolean addAll(int index, Collection<? extends E> c) { 704 int thisSize = this.size(); 705 for (Object element : c) { 706 requireNonNull(element, RESOURCES.getString("collectionElementsNullErr")); 707 if (!(element instanceof GeomPoint)) 708 throw new ClassCastException(MessageFormat.format( 709 RESOURCES.getString("listElementTypeErr"), "PointString", "GeomPoint")); 710 if (thisSize != 0) { 711 if (((GeomElement)element).getPhyDimension() != this.getPhyDimension()) 712 throw new DimensionException(RESOURCES.getString("dimensionErr")); 713 } 714 } 715 return super.addAll(index, c); 716 } 717 718 /** 719 * Holds the default XML representation for this object. 720 */ 721 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 722 protected static final XMLFormat<PointString> XML = new XMLFormat<PointString>(PointString.class) { 723 724 @Override 725 public PointString newInstance(Class<PointString> cls, XMLFormat.InputElement xml) throws XMLStreamException { 726 PointString obj = PointString.newInstance(); 727 return obj; 728 } 729 730 @Override 731 public void read(XMLFormat.InputElement xml, PointString obj) throws XMLStreamException { 732 AbstractPointGeomList.XML.read(xml, obj); // Call parent read. 733 } 734 735 @Override 736 public void write(PointString obj, XMLFormat.OutputElement xml) throws XMLStreamException { 737 AbstractPointGeomList.XML.write(obj, xml); // Call parent write. 738 } 739 }; 740 741 ////////////////////// 742 // Factory Creation // 743 ////////////////////// 744 private static final ObjectFactory<PointString> FACTORY = new ObjectFactory<PointString>() { 745 @Override 746 protected PointString create() { 747 return new PointString(); 748 } 749 @Override 750 protected void cleanup(PointString obj) { 751 obj.reset(); 752 } 753 }; 754 755 /** 756 * Recycles a case instance immediately (on the stack when executing in a 757 * StackContext). 758 * 759 * @param instance The instance to be recycled immediately. 760 */ 761 public static void recycle(PointString instance) { 762 FACTORY.recycle(instance); 763 } 764 765 /** 766 * Do not allow the default constructor to be used except by subclasses. 767 */ 768 protected PointString() {} 769 770 private static <E2 extends GeomPoint> PointString<E2> copyOf(PointString<E2> original) { 771 PointString<E2> o = PointString.newInstance(); 772 original.copyState(o); 773 int size = original.size(); 774 for (int i=0; i < size; ++i) { 775 E2 element = original.get(i); 776 o.add((E2)element.copy()); 777 } 778 return o; 779 } 780 781 /** 782 * Tests the methods in this class. 783 * 784 * @param args Command-line arguments (not used). 785 */ 786 public static void main(String args[]) { 787 System.out.println("Testing PointString:"); 788 789 Point p1 = Point.valueOf(1, 4, 6, NonSI.FOOT); 790 Point p2 = Point.valueOf(7, 2, 5, NonSI.FOOT); 791 Point p3 = Point.valueOf(10, 8, 3, NonSI.FOOT); 792 Point p4 = Point.valueOf(12, 11, 9, NonSI.FOOT); 793 PointString<Point> str1 = PointString.valueOf("A String", p1, p2, p3, p4); 794 str1.putUserData("Creator", "Joe Huwaldt"); 795 str1.putUserData("Date", "June 18, 2013"); 796 System.out.println("str1 = " + str1); 797 System.out.println("points = "); 798 for (GeomPoint point : str1) { 799 System.out.println(point); 800 } 801 802 Vector<Length> V = Vector.valueOf(SI.METER, 2, 0, 0); 803 PointString<?> str2 = str1.getTransformed(GTransform.newTranslation(V)); 804 System.out.println("\nTranslate str1 by V = " + V); 805 System.out.println("str2 = " + str2); 806 System.out.println("points = "); 807 for (GeomPoint point : str2) { 808 System.out.println(point); 809 } 810 811 V = Vector.valueOf(NonSI.FOOT, 0,2,0); 812 PointString<?> str3 = str2.getTransformed(GTransform.newTranslation(V)); 813 System.out.println("\nTranslate str2 by V = " + V); 814 System.out.println("str3 = " + str3); 815 System.out.println("points = "); 816 for (GeomPoint point : str3) { 817 System.out.println(point); 818 } 819 820 // Write out XML data. 821 try { 822 System.out.println(); 823 824 // Creates some useful aliases for class names. 825 javolution.xml.XMLBinding binding = new GeomXMLBinding(); 826 827 javolution.xml.XMLObjectWriter writer = javolution.xml.XMLObjectWriter.newInstance(System.out); 828 writer.setIndentation(" "); 829 writer.setBinding(binding); 830 writer.write(str1, "PointString", PointString.class); 831 writer.flush(); 832 833 System.out.println(); 834 } catch (Exception e) { 835 e.printStackTrace(); 836 } 837 838 } 839} 840 841