/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.math; import com.revolsys.geometry.algorithm.RobustDeterminant; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.impl.PointDoubleXY; import com.revolsys.geometry.util.Assert; import com.revolsys.math.Angle; import com.revolsys.util.MathUtil; /** * A 2-dimensional mathematical vector represented by double-precision X and Y components. * * @author mbdavis * */ public class Vector2D { /** * Creates a new vector with given X and Y components. * * @param x the x component * @param y the y component * @return a new vector */ public static Vector2D newVector(final double x, final double y) { return new Vector2D(x, y); } /** * Creates a vector from a {@link Coordinates}. * * @param coord the Point to copy * @return a new vector */ public static Vector2D newVector(final Point coord) { return new Vector2D(coord); } /** * Creates a vector with the direction and magnitude * of the difference between the * <tt>to</tt> and <tt>from</tt> {@link Coordinates}s. * * @param from the origin Coordinate * @param to the destination Coordinate * @return a new vector */ public static Vector2D newVector(final Point from, final Point to) { return new Vector2D(from, to); } /** * Creates a new vector from an existing one. * * @param v the vector to copy * @return a new vector */ public static Vector2D newVector(final Vector2D v) { return new Vector2D(v); } /** * The X component of this vector. */ private final double x; /** * The Y component of this vector. */ private final double y; public Vector2D() { this(0.0, 0.0); } public Vector2D(final double x, final double y) { this.x = x; this.y = y; } public Vector2D(final Point v) { this.x = v.getX(); this.y = v.getY(); } public Vector2D(final Point from, final Point to) { this.x = to.getX() - from.getX(); this.y = to.getY() - from.getY(); } public Vector2D(final Vector2D v) { this.x = v.x; this.y = v.y; } public Vector2D add(final Vector2D v) { return newVector(this.x + v.x, this.y + v.y); } public double angle() { return Math.atan2(this.y, this.x); } public double angle(final Vector2D v) { return Angle.angleDiff(v.angle(), angle(), false); } public double angleTo(final Vector2D v) { final double a1 = angle(); final double a2 = v.angle(); final double angDel = a2 - a1; // normalize, maintaining orientation if (angDel <= -Math.PI) { return angDel + Angle.PI_TIMES_2; } if (angDel > Math.PI) { return angDel - Angle.PI_TIMES_2; } return angDel; } public Vector2D average(final Vector2D v) { return weightedSum(v, 0.5); } /** * Creates a copy of this vector * * @return a copy of this vector */ @Override public Object clone() { return new Vector2D(this); } /** * Computes the distance between this vector and another one. * @param v a vector * @return the distance between the vectors */ public double distance(final Vector2D v) { final double delx = v.x - this.x; final double dely = v.y - this.y; return Math.sqrt(delx * delx + dely * dely); } /** * Divides the vector by a scalar value. * * @param d the value to divide by * @return a new vector with the value v / d */ public Vector2D divide(final double d) { return newVector(this.x / d, this.y / d); } /** * Computes the dot-product of two vectors * * @param v a vector * @return the dot product of the vectors */ public double dot(final Vector2D v) { return this.x * v.x + this.y * v.y; } /** * Tests if a vector <tt>o</tt> has the same values for the x and y * components. * * @param o * a <tt>Vector2D</tt> with which to do the comparison. * @return true if <tt>other</tt> is a <tt>Vector2D</tt> with the same * values for the x and y components. */ @Override public boolean equals(final Object o) { if (!(o instanceof Vector2D)) { return false; } final Vector2D v = (Vector2D)o; return this.x == v.x && this.y == v.y; } public double getComponent(final int index) { if (index == 0) { return this.x; } return this.y; } public double getX() { return this.x; } public double getY() { return this.y; } /** * Gets a hashcode for this vector. * * @return a hashcode for this vector */ @Override public int hashCode() { // Algorithm from Effective Java by Joshua Bloch int result = 17; result = 37 * result + MathUtil.hashCode(this.x); result = 37 * result + MathUtil.hashCode(this.y); return result; } public boolean isParallel(final Vector2D v) { return 0.0 == RobustDeterminant.signOfDet2x2(this.x, this.y, v.x, v.y); } public double length() { return Math.sqrt(this.x * this.x + this.y * this.y); } public double lengthSquared() { return this.x * this.x + this.y * this.y; } /** * Multiplies the vector by a scalar value. * * @param d the value to multiply by * @return a new vector with the value v * d */ public Vector2D multiply(final double d) { return newVector(this.x * d, this.y * d); } public Vector2D negate() { return newVector(-this.x, -this.y); } public Vector2D normalize() { final double length = length(); if (length > 0.0) { return divide(length); } return newVector(0.0, 0.0); } public Vector2D rotate(final double angle) { final double cos = Math.cos(angle); final double sin = Math.sin(angle); return newVector(this.x * cos - this.y * sin, this.x * sin + this.y * cos); } /** * Rotates a vector by a given number of quarter-circles (i.e. multiples of 90 * degrees or Pi/2 radians). A positive number rotates counter-clockwise, a * negative number rotates clockwise. Under this operation the magnitude of * the vector and the absolute values of the ordinates do not change, only * their sign and ordinate index. * * @param numQuarters * the number of quarter-circles to rotate by * @return the rotated vector. */ public Vector2D rotateByQuarterCircle(final int numQuarters) { int nQuad = numQuarters % 4; if (numQuarters < 0 && nQuad != 0) { nQuad = nQuad + 4; } switch (nQuad) { case 0: return newVector(this.x, this.y); case 1: return newVector(-this.y, this.x); case 2: return newVector(-this.x, -this.y); case 3: return newVector(this.y, -this.x); } Assert.shouldNeverReachHere(); return null; } public Vector2D subtract(final Vector2D v) { return newVector(this.x - v.x, this.y - v.y); } public Point toCoordinate() { return new PointDoubleXY(this.x, this.y); } /** * Gets a string representation of this vector * * @return a string representing this vector */ @Override public String toString() { return "[" + this.x + ", " + this.y + "]"; } public Point translate(final Point point) { return new PointDoubleXY(this.x + point.getX(), this.y + point.getY()); } /** * Computes the weighted sum of this vector * with another vector, * with this vector contributing a fraction * of <tt>frac</tt> to the total. * <p> * In other words, * <pre> * sum = frac * this + (1 - frac) * v * </pre> * * @param v the vector to sum * @param frac the fraction of the total contributed by this vector * @return the weighted sum of the two vectors */ public Vector2D weightedSum(final Vector2D v, final double frac) { return newVector(frac * this.x + (1.0 - frac) * v.x, frac * this.y + (1.0 - frac) * v.y); } }