
org.xmlcml.euclid.Real2Vector Maven / Gradle / Ivy
/**
* Copyright 2011 Peter Murray-Rust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmlcml.euclid;
import java.util.ArrayList;
import java.util.List;
import org.xmlcml.euclid.Axis.Axis2;
/**
* a (Java) Vector of Real2s (Note that 'Vector' is used by Java to describe an
* array of objects - there is no relationship to geometrical vectors in this
* package.)
*
* Support is also given for the two component arrays as RealArrays
*
* Default is an empty (Java) Vector;
*
* @author (C) P. Murray-Rust, 1996
*/
public class Real2Vector implements EuclidConstants {
List vector;
/**
* Comment for serialVersionUID
*/
private static final long serialVersionUID = 3834026952770990647L;
/**
* constructor.
*/
public Real2Vector() {
vector = new ArrayList();
}
/**
* Formed by feeding in an existing array to a 2xn matrix THE COLUMN IS THE
* FASTEST MOVING INDEX, that is the matrix is filled as flarray(0,0,
* flarray(0,1). Primarily for compatibility with other apps
*
* @param flarray
* @exception EuclidRuntimeException
* array must have even number of elements
*
*/
public Real2Vector(double[] flarray) throws EuclidRuntimeException {
this();
int count = 0;
int n = flarray.length / 2;
if (flarray.length != 2 * n) {
throw new EuclidRuntimeException("size must be multiple of 2");
}
for (int i = 0; i < n; i++) {
Real2 p = new Real2(flarray[count++], flarray[count++]);
vector.add(p);
}
}
/**
* from two parallel arrays of x, y - by REFERENCE.
*
* @param n
* @param x
* @param y
* @throws EuclidRuntimeException
*
*/
public Real2Vector(int n, double[] x, double[] y) throws EuclidRuntimeException {
this();
Util.check(x, n);
Util.check(y, n);
for (int i = 0; i < n; i++) {
vector.add(new Real2(x[i], y[i]));
}
}
/** conversion routine.
*
* @param r2a
*/
public Real2Vector(Real2Array r2a) {
this(r2a.size(), r2a.getXArray().getArray(), r2a.getYArray().getArray());
}
/**
* constructor from RealArray - by REFERENCE
*
* @param m
* @exception EuclidRuntimeException
* array must have even number of elements
*/
public Real2Vector(RealArray m) throws EuclidRuntimeException {
this();
int count = m.size() / 2;
if (m.size() != count * 2) {
throw new EuclidRuntimeException("size must be multiple of 2");
}
double[] marray = m.getArray();
int j = 0;
for (int i = 0; i < count; i++) {
vector.add(new Real2(marray[j++], marray[j++]));
}
}
/**
* copy constructor from Real2Vector COPIES pv
*
* @param pv
*/
public Real2Vector(Real2Vector pv) {
this();
for (int i = 0; i < pv.size(); i++) {
vector.add(new Real2(pv.get(i)));
}
}
/**
* construct from list of Real2.
*
* @param rList
* list
*/
public Real2Vector(List rList) {
this();
for (Real2 r : rList) {
add(r);
}
}
/**
* appens a Real2.
*
* @param p
* to append
*/
public void add(Real2 p) {
vector.add(p);
}
/**
* gets Real2 element.
*
* @param i
* the position
* @return the Real2 or null if outside range
*/
public Real2 get(int i) {
return (Real2) vector.get(i);
}
/**
* @param i
* @param v
* @exception EuclidRuntimeException
* vector does not have an i'th element
*/
public void set(int i, Real2 v) throws EuclidRuntimeException {
vector.set(i, v);
}
/**
* size of vector.
*
* @return the size
*/
public int size() {
return vector.size();
}
/** access the vector.
* this is the only storage so should be safe
* @return vector
*/
public List getVector() {
return vector;
}
/**
* get range of one coordinate
*
* @param ax
* @return range
*/
public RealRange getRange(Axis2 ax) {
RealArray temp = new RealArray(vector.size());
double[] dd = temp.getArray();
int i = 0;
for (Real2 p : vector) {
dd[i++] = (ax.equals(Axis2.X)) ? p.getX() : p.getY();
}
RealRange range = new RealRange();
if (size() > 0) {
range.add(temp.smallestElement());
range.add(temp.largestElement());
}
return range;
}
/**
* get range of both coordinates
*
* @return range
*/
public Real2Range getRange2() {
Axis2 axes[] = Axis2.values();
Real2Range range2 = new Real2Range();
for (Axis2 ax : axes) {
RealRange range = getRange(ax);
range2.add(ax, range);
}
return range2;
}
/**
* create a NEW subset of the points; points are COPIED
*
* @param is
* @return sub array
* @exception EuclidRuntimeException
* an element of is is out of range of this
*/
public Real2Vector subArray(IntSet is) throws EuclidRuntimeException {
Real2Vector sub = new Real2Vector();
for (int i = 0; i < is.size(); i++) {
int ix = is.elementAt(i);
if (ix < 0 || ix >= vector.size()) {
throw new EuclidRuntimeException("index out of range " + ix);
}
sub.add(new Real2(this.getReal2(ix)));
}
return sub;
}
/**
* create a subset of the points within a box
*
* @param r
* @return sub set
*/
public IntSet subSet(Real2Range r) {
IntSet is = new IntSet();
for (int i = 0; i < size(); i++) {
Real2 point = (Real2) vector.get(i);
if (r.includes(point)) {
is.addElement(i);
}
}
return is;
}
/**
* get the closest point (both ranges are assumed to have the same scales
*
* @param p
* @return index of point
*/
public int getClosestPoint(Real2 p) {
double dist2 = Double.POSITIVE_INFINITY;
int ipoint = -1;
for (int i = 0; i < size(); i++) {
Real2 point = this.get(i);
double dx = p.x - point.x;
double dy = p.y - point.y;
double d2 = dx * dx + dy * dy;
if (d2 < dist2) {
dist2 = d2;
ipoint = i;
}
}
return ipoint;
}
/**
* get the index of the first point within a box centered on p (that is p+-
* width/2, height/2) or -1 if none
*
* @param p
* @param width
* @param height
* @return index of point
*
*/
public int getPoint(Real2 p, double width, double height) {
double hwidth = width / 2.0;
double hheight = height / 2.0;
for (int i = 0; i < size(); i++) {
Real2 point = this.get(i);
if (Math.abs(p.x - point.x) <= hwidth
&& Math.abs(p.y - point.y) <= hheight)
return i;
}
return -1;
}
/**
* translate by a vector, synonym for plus. MODIFIES 'this'
*
* @param v
*/
public void translateBy(Real2 v) {
for (int i = 0; i < size(); i++) {
Real2 p = getReal2(i).plus(v);
vector.set(i, p);
}
}
/**
* add a Real2 to all elements of this. MODIFIES 'this'
*
* @param p
*/
public void plus(Real2 p) {
this.translateBy(p);
}
/**
* translate negatively; MODIFIES 'this'
*
* @param v
*/
public void subtract(Real2 v) {
Real2 v1 = new Real2(v);
v1.negative();
this.plus(v1);
}
/**
* multiply all coordinates be a given scalar (that is expands scale)
*
* @param f
*/
public void multiplyBy(double f) {
for (int i = 0; i < size(); i++) {
Real2 p = getReal2(i).multiplyBy(f);
vector.set(i, p);
}
}
/**
* get distance between 2 points
*
* @param i1
* @param i2
* @return distance
*/
public double distance(int i1, int i2) {
Real2 v1 = getReal2(i1).subtract(getReal2(i2));
return v1.getLength();
}
/**
* get distance between 2 points
*
* @param is
* @return distance
* @exception EuclidRuntimeException
* a value in IntSet is not in the range 0 ... nelem-1
*/
public double distance(IntSet is) throws EuclidRuntimeException {
if (is.size() != 2) {
throw new EuclidRuntimeException("index must be multiple of 2");
}
return distance(is.elementAt(0), is.elementAt(1));
}
/**
* get angle between 3 points
*
* @param i1
* @param i2
* @param i3
* @return angle
* @exception EuclidRuntimeException
* two points are coincident
*/
public Angle angle(int i1, int i2, int i3) throws EuclidRuntimeException {
return Real2.getAngle(getReal2(i1), getReal2(i2), getReal2(i3));
}
/**
* get angle between 3 points
*
* @param is
* @return angle
* @exception EuclidRuntimeException
* a value in IntSet is not in the range 0 ... nelem-1
*/
public Angle angle(IntSet is) throws EuclidRuntimeException {
if (is.size() != 3) {
throw new EuclidRuntimeException("index must be multiple of 3");
}
return angle(is.elementAt(0), is.elementAt(1), is.elementAt(2));
}
/**
* get the i'th Real2
*
* @param i
* @return element
*/
public Real2 getReal2(int i) {
return (Real2) get(i);
}
/** get List of Real2s.
*
* @return the list
*/
public List getReal2List() {
return vector;
}
/**
* get the coordinate coordinate array as doubles x,y,x,y,
*
* @return array
*/
public RealArray getXY() {
double[] f = new double[2 * size()];
int count = 0;
for (int i = 0; i < size(); i++) {
Real2 p = getReal2(i);
f[count++] = p.getX();
f[count++] = p.getY();
}
return new RealArray(f);
}
/**
* get the X coordinate array
*
* @return array
*/
public RealArray getXArray() {
double[] f = new double[size()];
int count = 0;
for (int i = 0; i < size(); i++) {
Real2 p = getReal2(i);
f[count++] = p.getX();
}
return new RealArray(f);
}
/**
* get the Y coordinate array
*
* @return array
*/
public RealArray getYArray() {
double[] f = new double[size()];
int count = 0;
for (int i = 0; i < size(); i++) {
Real2 p = getReal2(i);
f[count++] = p.getY();
}
return new RealArray(f);
}
/** convenience
*
* @return real2Array
*/
public Real2Array getReal2Array() {
return new Real2Array(getXArray(), getYArray());
}
/**
* get a single coordinate value
*
* @param i
* @param j
* @return coord
*/
public double getCoordinate(int i, Axis2 j) {
return (j.value == 0) ? getReal2(i).getX() : getReal2(i).getY();
}
/**
* get a single coordinate array - for example all x-coordinates
*
* @param axis
* @return array
*/
public RealArray getXorY(Axis2 axis) {
double[] f = new double[size()];
for (int i = 0; i < size(); i++) {
f[i] = (axis.value == 0) ? getReal2(i).getX() : getReal2(i).getY();
}
return new RealArray(f);
}
/**
* swap all X and Y coordinates; MODIFIES array
*/
public void swapXY() {
for (int i = 0; i < size(); i++) {
Real2 temp = (Real2) vector.get(i);
temp.swap();
vector.set(i, temp);
}
}
/**
* sort ARRAY on X or Y coordinate; returns new array
*
* @param ax
* @return vector
*/
public Real2Vector sortAscending(Axis2 ax) {
IntSet is = getXorY(ax).indexSortAscending();
Real2Vector temp = new Real2Vector();
for (int i = 0; i < size(); i++) {
temp.add(this.get(is.elementAt(i)));
}
return temp;
}
/**
* sort ARRAY on X or Y coordinate; returns new array
*
* @param ax
* @return vector
*
*/
public Real2Vector sortDescending(Axis2 ax) {
IntSet is = getXorY(ax).indexSortDescending();
Real2Vector temp = new Real2Vector();
for (int i = 0; i < size(); i++) {
temp.add(this.get(is.elementAt(i)));
}
return temp;
}
/**
* transforms 'this' by rotation-translation matrix; MODIFIES 'this'
*
* @param t
*/
public void transformBy(Transform2 t) {
for (int i = 0; i < size(); i++) {
Real2 point = (Real2) vector.get(i);
point.transformBy(t);
vector.set(i, point);
}
}
/**
* squared difference between corresponding points in 2 real2Vectors. must
* be of same length else returns Double.NaN
*
* @param r2v
* vector to compare
* @return sum ((x1-x2)**2 + (y1-y2)**2)
*/
public double getSquaredDifference(Real2Vector r2v) {
double sum = Double.NaN;
if (r2v.size() == 0 || vector.size() == 0
|| r2v.size() != vector.size()) {
} else {
sum = 0.0;
for (int i = 0; i < size(); i++) {
Real2 xy1 = this.get(i);
Real2 xy2 = r2v.get(i);
double d2 = xy1.getSquaredDistance(xy2);
sum += d2;
}
}
return sum;
}
/**
* gets array of squared distances between corresponding points.
*
* array of d(this(i)-r2v(i))^2; returns null if Real2Vectors are different
* lengths
*
* @param r2v
* other vector
* @return array of squares distances else null
*/
public double[] getSquaredDistances(Real2Vector r2v) {
double dist2[] = null;
if (r2v.size() == vector.size()) {
dist2 = new double[vector.size()];
for (int i = 0; i < size(); i++) {
Real2 xy1 = this.get(i);
Real2 xy2 = r2v.get(i);
dist2[i] = xy1.getSquaredDistance(xy2);
}
}
return dist2;
}
/**
* rotate about centroid by given angle; MODIFIES 'this'
*
* @param a
*/
public void rotateAboutCentroid(Angle a) {
Real2 temp = this.getCentroid();
Transform2 t2 = new Transform2(a);
t2 = new Transform2(t2, temp);
this.transformBy(t2);
}
/** creates mirror image
* changes sign of x-coords
*/
public void flipX() {
for (Real2 r2 : vector) {
r2.setX(-r2.getX());
}
}
/** creates mirror image
* changes sign of y-coords
*/
public void flipY() {
for (Real2 r2 : vector) {
r2.setY(-r2.getY());
}
}
/**
* create regular polygon.
* centre is at 0.0, first point at
* (rad*cos(angle+dangle), rad*sin(angle+dangle))
* points go anticlockwise
*
* @param nsides
* @param rad the radius
* @param angle offset to vertical (radians)
* (point 0 is at rad*cos(angle), rad*sin(angle))
* @return array of points
*/
public static Real2Vector regularPolygon(int nsides, double rad, double angle) {
Real2Vector temp = new Real2Vector();
double dangle = 2 * Math.PI / ((double)nsides);
for (int i = 0; i < nsides; i++) {
Real2 p = new Real2(rad * Math.sin(angle), rad * Math.cos(angle));
temp.add(p);
angle += dangle;
}
return temp;
}
/**
* create part of regular polygon.
* centre is at 0.0, last point at (rad, 0)
* points go anticlockwise
* produces points pointn, pointn+1, ... nsides-1, 0
* if npoint is 1 we get full polygon
* if npoint {@literal >}= nsides returns null
* @param nsides
* @param pointn
* @param dist0n between point0 and npoint
* @return array of points
*/
public static Real2Vector partOfRegularPolygon(int nsides, int pointn, double dist0n) {
Real2Vector temp = new Real2Vector();
double dangle = 2 * Math.PI / ((double)nsides);
double sinAngle2 = Math.sin((double) pointn * dangle / 2.);
double rad = 0.5 * dist0n / sinAngle2;
double angle = dangle;
for (int i = pointn; i <= nsides; i++) {
Real2 p = new Real2(rad * Math.sin(angle), rad * Math.cos(angle));
temp.add(p);
angle += dangle;
}
return temp;
}
/**
* create regular polygon.
* centre is at 0.0, first point at (0, rad)
* points go anticlockwise (rad*cos, -rad*sin)
*
* @param nsides
* @param rad the radius
* @return array of points
*/
public static Real2Vector regularPolygon(int nsides, double rad) {
return regularPolygon(nsides, rad, 0.0);
}
/** create regular polygon.
* goes through points p1, p2
* if m = (p1 +p2)/2
* and c is centre of circle
* and pp = p2 - p1
* and d = c-m
* then signed angle(pp, d) is 90
*
* points go clockwise
* to make the polygon go the other way reverse p1 and p2
* @param nsides
* @param p1 first point
* @param p2 second point
* @param flip use mirror image
* @return array of points
*/
public static Real2Vector regularPolygon(int nsides, Real2 p1, Real2 p2, boolean flip) {
double halfAngle = Math.PI / (double) nsides;
Vector2 p1top2 = new Vector2(p2.subtract(p1));
double halfSidelength = p1top2.getLength() / 2.0;
double rad = halfSidelength / Math.sin(halfAngle);
Real2Vector temp = regularPolygon(nsides, rad);
if (flip) {
temp.flipY();
}
// deviation between p1p2 and the y-axis
Angle rot = null;
rot = p1top2.getAngleMadeWith(new Vector2(0.0, 1.0));
rot = new Vector2(1.0, 0.0).getAngleMadeWith(p1top2);
temp.transformBy(new Transform2(rot));
temp.transformBy(new Transform2(new Angle(-halfAngle)));
Real2 shift = p1.subtract(new Vector2(temp.get(0)));
temp.transformBy(new Transform2(new Vector2(shift)));
return temp;
}
/**
* get centroid of all points. does not modify this
*
* @return the centroid
*/
public Real2 getCentroid() {
if (vector.size() < 1)
return null;
Real2 p = new Real2();
for (int j = 0; j < vector.size(); j++) {
Real2 pp = (Real2) vector.get(j);
p = p.plus(pp);
}
double scale = 1.0 / ((double)vector.size());
p = p.multiplyBy(scale);
return p;
}
/**
* get serialNumber of nearest point.
*
* @param point
* @return serial of point
*/
public int getSerialOfNearestPoint(Real2 point) {
double dist = Double.MAX_VALUE;
int serial = -1;
for (int j = 0; j < vector.size(); j++) {
double d = this.get(j).subtract(point).getLength();
if (d < dist) {
serial = j;
dist = d;
}
}
return serial;
}
/**
* get rectangular distance matrix.
*
* matrix need not be square or symmetric unless coords == coords2 d(ij) is
* distance between coord(i) and coords2(j).
*
* @param coords2
* @return distance matrix
*/
public RealMatrix getDistanceMatrix(List coords2) {
int size = vector.size();
int size2 = coords2.size();
RealMatrix distMatrix = new RealMatrix(size, size2);
double[][] mat = distMatrix.getMatrix();
for (int i = 0; i < size; i++) {
Real2 ri = this.get(i);
for (int j = 0; j < size2; j++) {
Real2 rj = coords2.get(j);
double dij = ri.getDistance(rj);
mat[i][j] = dij;
}
}
return distMatrix;
}
/**
* if real2Vector is treated as a polygon, determines whether point
* is inside it
// The function will return true if the point x,y is inside the polygon, or
// false if it is not. If the point is exactly on the edge of the polygon,
// then the function may return YES or NO.
*/
public boolean encloses(Real2 point) {
int nvect = vector.size();
int j = nvect - 1;
boolean oddNodes = false;
double x = point.getX();
double y = point.getY();
for (int i=0; i < nvect; i++) {
double xi = vector.get(i).getX();
double yi = vector.get(i).getY();
double xj = vector.get(j).getX();
double yj = vector.get(j).getY();
if (yi < y && yj >= y || yj < y && yi >= y) {
if (xi + (y - yi) / (yj - yi) * (xj - xi) < x) {
oddNodes = !oddNodes;
}
}
j = i;
}
return oddNodes;
}
/**
* to string.
*
* @return string
*/
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(S_LBRAK);
for (int i = 0; i < size(); i++) {
sb.append(getReal2(i));
if (i < (size() - 1)) {
sb.append(S_NEWLINE);
}
}
sb.append(S_RBRAK);
return sb.toString();
}
}