001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.math.geometry.point; 031 032import java.util.ArrayList; 033import java.util.Collection; 034import java.util.Iterator; 035import java.util.List; 036 037import org.openimaj.math.geometry.GeometricObject2d; 038import org.openimaj.math.geometry.line.Line2d; 039import org.openimaj.math.geometry.shape.Polygon; 040import org.openimaj.math.geometry.shape.Rectangle; 041import org.openimaj.math.geometry.shape.util.GrahamScan; 042 043import Jama.Matrix; 044 045/** 046 * A base implementation of a {@link GeometricObject2d} that is a <b>set</b> of 047 * points in space. Even though the points are backed by a list, the class 048 * itself does not make any assumptions about the order of the points (i.e. to 049 * determine connectedness), however, subclasses might. 050 * 051 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 052 */ 053public class PointList implements GeometricObject2d, Iterable<Point2d>, Cloneable { 054 /** The points in the {@link PointList} */ 055 public List<Point2d> points = new ArrayList<Point2d>(); 056 057 /** 058 * Construct a {@link PointList} from points 059 * 060 * @param points 061 * the points 062 */ 063 public PointList(Point2d... points) { 064 for (final Point2d v : points) 065 this.points.add(v); 066 } 067 068 /** 069 * Construct a {@link PointList} from points 070 * 071 * @param points 072 * the points 073 */ 074 public PointList(Collection<? extends Point2d> points) { 075 this(points, false); 076 } 077 078 /** 079 * Construct a {@link PointList} from the points, possibly copying the 080 * points first 081 * 082 * @param points 083 * the points 084 * @param copy 085 * should the points be copied 086 */ 087 public PointList(Collection<? extends Point2d> points, boolean copy) { 088 if (!copy) 089 this.points.addAll(points); 090 else 091 { 092 for (final Point2d p : points) 093 this.points.add(p.copy()); 094 } 095 } 096 097 void rotate(Point2d point, Point2d origin, double angle) { 098 final double X = origin.getX() + ((point.getX() - origin.getX()) * Math.cos(angle) - 099 (point.getY() - origin.getY()) * Math.sin(angle)); 100 101 final double Y = origin.getY() + ((point.getX() - origin.getX()) * Math.sin(angle) + 102 (point.getY() - origin.getY()) * Math.cos(angle)); 103 104 point.setX((float) X); 105 point.setY((float) Y); 106 } 107 108 /** 109 * Rotate the {@link PointList} about the given origin with the given angle 110 * (in radians) 111 * 112 * @param origin 113 * the origin of the rotation 114 * @param angle 115 * the angle in radians 116 */ 117 public void rotate(Point2d origin, double angle) { 118 for (final Point2d p : points) 119 rotate(p, origin, angle); 120 } 121 122 /** 123 * Rotate the {@link PointList} about (0,0) with the given angle (in 124 * radians) 125 * 126 * @param angle 127 * the angle in radians 128 */ 129 public void rotate(double angle) { 130 this.rotate(new Point2dImpl(0, 0), angle); 131 } 132 133 /** 134 * Compute the regular (oriented to the axes) bounding box of the 135 * {@link PointList}. 136 * 137 * @return the regular bounding box 138 */ 139 @Override 140 public Rectangle calculateRegularBoundingBox() { 141 int xmin = Integer.MAX_VALUE, xmax = 0, ymin = Integer.MAX_VALUE, ymax = 0; 142 143 for (final Point2d p : points) { 144 if (p.getX() < xmin) 145 xmin = (int) Math.floor(p.getX()); 146 if (p.getX() > xmax) 147 xmax = (int) Math.ceil(p.getX()); 148 if (p.getY() < ymin) 149 ymin = (int) Math.floor(p.getY()); 150 if (p.getY() > ymax) 151 ymax = (int) Math.ceil(p.getY()); 152 } 153 154 return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); 155 } 156 157 /** 158 * Translate the {@link PointList}s position 159 * 160 * @param x 161 * x-translation 162 * @param y 163 * y-translation 164 */ 165 @Override 166 public void translate(float x, float y) { 167 for (final Point2d p : points) { 168 p.setX(p.getX() + x); 169 p.setY(p.getY() + y); 170 } 171 } 172 173 /** 174 * Scale the {@link PointList} by the given amount about (0,0). Scalefactors 175 * between 0 and 1 shrink the {@link PointList}. 176 * 177 * @param sc 178 * the scale factor. 179 */ 180 @Override 181 public void scale(float sc) { 182 for (final Point2d p : points) { 183 p.setX(p.getX() * sc); 184 p.setY(p.getY() * sc); 185 } 186 } 187 188 /** 189 * Scale the {@link PointList} only in the x-direction by the given amount 190 * about (0,0). Scale factors between 0 and 1 will shrink the 191 * {@link PointList} 192 * 193 * @param sc 194 * The scale factor 195 * @return this {@link PointList} 196 */ 197 public PointList scaleX(float sc) 198 { 199 for (final Point2d p : points) { 200 p.setX(p.getX() * sc); 201 } 202 return this; 203 } 204 205 /** 206 * Scale the {@link PointList} only in the y-direction by the given amount 207 * about (0,0). Scale factors between 0 and 1 will shrink the 208 * {@link PointList} 209 * 210 * @param sc 211 * The scale factor 212 * @return this {@link PointList} 213 */ 214 public PointList scaleY(float sc) 215 { 216 for (final Point2d p : points) { 217 p.setY(p.getY() * sc); 218 } 219 return this; 220 } 221 222 /** 223 * Scale the {@link PointList} by the given amount about (0,0). Scale 224 * factors between 0 and 1 shrink the {@link PointList}. 225 * 226 * @param scx 227 * the scale factor in the x direction 228 * @param scy 229 * the scale factor in the y direction. 230 * @return this {@link PointList} 231 */ 232 public PointList scaleXY(float scx, float scy) 233 { 234 for (final Point2d p : points) { 235 p.setX(p.getX() * scx); 236 p.setY(p.getY() * scy); 237 } 238 return this; 239 } 240 241 /** 242 * Scale the {@link PointList} by the given amount about the given point. 243 * Scalefactors between 0 and 1 shrink the {@link PointList}. 244 * 245 * @param centre 246 * the centre of the scaling operation 247 * @param sc 248 * the scale factor 249 */ 250 @Override 251 public void scale(Point2d centre, float sc) { 252 this.translate(-centre.getX(), -centre.getY()); 253 254 for (final Point2d p : points) { 255 p.setX(p.getX() * sc); 256 p.setY(p.getY() * sc); 257 } 258 259 this.translate(centre.getX(), centre.getY()); 260 } 261 262 /** 263 * Scale the {@link PointList} about its centre of gravity. Scalefactors 264 * between 0 and 1 shrink the {@link PointList}. 265 * 266 * @param sc 267 * the scale factor 268 */ 269 @Override 270 public void scaleCentroid(float sc) 271 { 272 final Point2d cog = calculateCentroid(); 273 this.translate(-cog.getX(), -cog.getY()); 274 this.scale(sc); 275 this.translate(cog.getX(), cog.getY()); 276 } 277 278 /** 279 * Get the centre of gravity of the {@link PointList} 280 * 281 * @return the centre of gravity of the {@link PointList} 282 */ 283 @Override 284 public Point2d calculateCentroid() 285 { 286 float xSum = 0; 287 float ySum = 0; 288 289 int n = 0; 290 291 for (final Point2d p : points) { 292 xSum += p.getX(); 293 ySum += p.getY(); 294 n++; 295 } 296 297 xSum /= n; 298 ySum /= n; 299 300 return new Point2dImpl(xSum, ySum); 301 } 302 303 /** 304 * @return the minimum x-ordinate of all vertices 305 */ 306 @Override 307 public double minX() { 308 return calculateRegularBoundingBox().x; 309 } 310 311 /** 312 * @return the minimum y-ordinate of all vertices 313 */ 314 @Override 315 public double minY() { 316 return calculateRegularBoundingBox().y; 317 } 318 319 /** 320 * @return the maximum x-ordinate of all vertices 321 */ 322 @Override 323 public double maxX() { 324 final Rectangle r = calculateRegularBoundingBox(); 325 return r.x + r.width; 326 } 327 328 /** 329 * @return the maximum y-ordinate of all vertices 330 */ 331 @Override 332 public double maxY() { 333 final Rectangle r = calculateRegularBoundingBox(); 334 return r.y + r.height; 335 } 336 337 /** 338 * @return the width of the regular bounding box 339 */ 340 @Override 341 public double getWidth() { 342 return maxX() - minX(); 343 } 344 345 /** 346 * @return the height of the regular bounding box 347 */ 348 @Override 349 public double getHeight() { 350 return maxY() - minY(); 351 } 352 353 /** 354 * Apply a 3x3 transform matrix to a copy of the {@link PointList} and 355 * return it 356 * 357 * @param transform 358 * 3x3 transform matrix 359 * @return the transformed {@link PointList} 360 */ 361 @Override 362 public PointList transform(Matrix transform) { 363 final List<Point2d> newVertices = new ArrayList<Point2d>(); 364 365 for (final Point2d p : points) { 366 final Matrix p1 = new Matrix(3, 1); 367 p1.set(0, 0, p.getX()); 368 p1.set(1, 0, p.getY()); 369 p1.set(2, 0, 1); 370 371 final Matrix p2_est = transform.times(p1); 372 373 final Point2d out = new Point2dImpl((float) (p2_est.get(0, 0) / p2_est.get(2, 0)), 374 (float) (p2_est.get(1, 0) / p2_est.get(2, 0))); 375 376 newVertices.add(out); 377 } 378 379 return new PointList(newVertices); 380 } 381 382 /** 383 * {@inheritDoc} 384 * 385 * @see java.lang.Iterable#iterator() 386 */ 387 @Override 388 public Iterator<Point2d> iterator() 389 { 390 return points.iterator(); 391 } 392 393 @Override 394 public String toString() { 395 return points.toString(); 396 } 397 398 /** 399 * Compute the mean of a set of {@link PointList}s. It is assumed that the 400 * number of points in the {@link PointList}s is equal, and that their is a 401 * one-to-one correspondance between the ith point in each list. 402 * 403 * @param shapes 404 * the shapes to average 405 * @return the average shape 406 */ 407 public static PointList computeMean(Collection<PointList> shapes) { 408 final int npoints = shapes.iterator().next().points.size(); 409 final PointList mean = new PointList(); 410 411 for (int i = 0; i < npoints; i++) 412 mean.points.add(new Point2dImpl()); 413 414 for (final PointList shape : shapes) { 415 for (int i = 0; i < npoints; i++) { 416 final Point2dImpl pt = (Point2dImpl) mean.points.get(i); 417 418 pt.x += shape.points.get(i).getX(); 419 pt.y += shape.points.get(i).getY(); 420 } 421 } 422 423 for (int i = 0; i < npoints; i++) { 424 final Point2dImpl pt = (Point2dImpl) mean.points.get(i); 425 426 pt.x /= shapes.size(); 427 pt.y /= shapes.size(); 428 } 429 430 return mean; 431 } 432 433 /** 434 * @return the number of points in the list 435 */ 436 public int size() { 437 return points.size(); 438 } 439 440 /** 441 * Calculate the intrinsic scale of the shape. This is the RMS distance of 442 * all the points from the centroid. 443 * 444 * @return the scale of the object. 445 */ 446 public float computeIntrinsicScale() { 447 final Point2d cog = this.calculateCentroid(); 448 float scale = 0; 449 450 for (final Point2d pt : this) { 451 final double x = pt.getX() - cog.getX(); 452 final double y = pt.getY() - cog.getY(); 453 454 scale += x * x + y * y; 455 } 456 457 return (float) Math.sqrt(scale / points.size()); 458 } 459 460 /** 461 * Get the ith point 462 * 463 * @param i 464 * the index of the point 465 * @return the ith point 466 */ 467 public Point2d get(int i) { 468 return points.get(i); 469 } 470 471 /** 472 * @return A list of {@link Line2d} assuming the points in this list are 473 * connected in order 474 */ 475 public List<Line2d> getLines() { 476 final List<Line2d> lines = new ArrayList<Line2d>(points.size() - 1); 477 478 for (int i = 1; i < this.points.size(); i++) { 479 lines.add(new Line2d( 480 points.get(i - 1), 481 points.get(i) 482 )); 483 } 484 485 return lines; 486 } 487 488 /** 489 * @param conns 490 * @return calls {@link PointListConnections#getLines(PointList)} with this 491 */ 492 public List<Line2d> getLines(PointListConnections conns) { 493 return conns.getLines(this); 494 } 495 496 @Override 497 public PointList clone() { 498 final PointList p = new PointList(); 499 for (final Point2d point2d : this) { 500 p.points.add(point2d.copy()); 501 } 502 return p; 503 } 504 505 /** 506 * Calculate the convex hull of the points using the Graham Scan algorithm. 507 * 508 * @see GrahamScan 509 * @return the convex hull 510 */ 511 public Polygon calculateConvexHull() { 512 return GrahamScan.getConvexHull(this.points); 513 } 514}