/*
* Transformation.java
*
* Created on March 23, 2005, 11:44 AM
*/
package ika.transformation;
import ika.geo.*;
import java.io.*;
import ika.utils.NumberFormatter;
import java.text.DecimalFormat;
/**
* Base class for geometric 2D transformations between
* two sets of points, where each set defines a coordinate system.
* @author Bernhard Jenny
* Institute of Cartography
* ETH Zurich
* jenny@karto.baug.ethz.ch
*/
public abstract class Transformation implements Serializable {
private static final long serialVersionUID = -8347757266449618041L;
/**
* Number of points used to compute the transformation parameters. Equals 0
* if the transformation has not been initialized yet.
*/
protected int numberOfPoints;
/**
* The residuals between the two point sets, with the source set transformed
* to the destination set.
* Only not null and valid after a call to init().
*/
protected double[][] v;
/**
* Returns the number of points used to compute the parameters
* of this transformation.
* @return The number of points
*/
public int getNumberOfPoints() {
return this.numberOfPoints;
}
/**
* Returns whether this transformation has been initialized.
* @return True if initialized.
*/
public boolean isInitialized() {
return this.numberOfPoints > 0;
}
/**
* Returns the residuals between the two point sets, with the source set
* transformed to the destination set.
*/
public double[][] getResiduals() {
return this.v;
}
/**
* Returns the name of the transformation.
* @return The name.
*/
public abstract String getName();
/**
* Returns an extensive description of the transformation,
* containing the transformation parameters and their precision.
* @return The description of the transformation.
*/
public abstract String getReport(boolean invert);
/**
* Returns a short description of the transformation,
* containing only essential transformation parameters and their precision.
* @return The description of the transformation.
*/
public abstract String getShortReport(boolean invert);
/**
* Returns a very short description of the transformation,
* containing only scale and rotation of the transformation.
* @return The description of the transformation.
*/
public String getSimpleShortReport(boolean invert) {
StringBuffer str = new StringBuffer(1024);
str.append(NumberFormatter.formatScale("Scale", this.getScale(invert)));
str.append("\n");
str.append(this.formatRotation("Rotation", this.getRotation(invert)));
str.append("\n");
str.append("(rounded values)\n");
return str.toString();
}
/**
* Returns a short description of the characteristics of the
* transformation. The description should not contain any numerical
* values.
* @return The description.
*/
public abstract String getShortDescription();
public abstract double getScale();
public double getScale(boolean invert) {
return invert ? 1. / this.getScale() : this.getScale();
}
public abstract double getRotation();
public double getRotation (boolean invert) {
return -this.getRotation();
}
public abstract double getSigma0();
// standard error of position = mittlerer Punktfehler
public double getStandardErrorOfPosition() {
return this.getSigma0() * Math.sqrt(2.d);
}
public abstract java.awt.geom.AffineTransform getAffineTransform();
/**
* Initialize the transformation with two set of control points.
* The control points are not copied, nor is a reference to them
* retained. Instead, the transformation parameters are
* immmediately computed by initWithPoints.
* @param destSet A two-dimensional array (nx2) containing the x and y
* coordinates of the points of the destination set.
* The destSet must be of exactly the same size as sourceSet.
* @param sourceSet A two-dimensional array (nx2) containing the x and y
* coordinates of the points of the source set.
* The sourceSet must be of exactly the same size as destSet.
*/
protected abstract void initWithPoints(double[][] destSet, double[][] sourceSet);
public void init(double[][] destSet, double[][] sourceSet) {
// make sure both sets have the same number of points.
if (destSet.length != sourceSet.length)
throw new IllegalArgumentException();
this.numberOfPoints = destSet.length;
this.v = new double[this.numberOfPoints][2];
this.initWithPoints(destSet, sourceSet);
}
public void addResidualsToPoints(double[][] pts) {
// make sure there is the same number of points as residuals.
if (pts.length != this.v.length)
throw new IllegalArgumentException();
final int nbrPts = pts.length;
for (int i = 0; i < nbrPts; ++i) {
pts[i][0] += this.v[i][0];
pts[i][1] += this.v[i][1];
}
}
public void substractResidualsFromPoints(double[][] pts) {
// make sure there are is the same number of points as residuals.
if (pts.length != this.v.length)
throw new IllegalArgumentException();
final int nbrPts = pts.length;
for (int i = 0; i < nbrPts; ++i) {
pts[i][0] -= this.v[i][0];
pts[i][1] -= this.v[i][1];
}
}
/**
* Transform a point from the coordinate system of the source set
* to the coordinate system of destination set.
* @return The transformed coordinates (array of size 1x2).
* @param point The point to be transformed (array of size 1x2).
*/
public abstract double[] transform(double[] point);
/**
* Transform an array of points from the coordinate system of the source set
* to the coordinate system of destination set.
* The transformed points overwrite the original values in the points[] array.
* @param points The points to be transformed.
* @param xColumn The column of points[] containing the x-coordinate.
* @param yColumn The column of points[] containing the y-coordinate.
*/
public void transform(double[][] points, int xColumn, int yColumn) {
this.transform(points, xColumn, yColumn, 0, points.length);
}
/**
* Transform an array of points from the coordinate system of the source set
* to the coordinate system of destination set.
* The transformed points overwrite the original values in the points[] array.
* This method should be overwritten by derived classes to provide a more
* efficient implementation that avoids multiple calls of transform().
* @param points The array holding the points to be transformed.
* @param xColumn The column of points[] containing the x-coordinate.
* @param yColumn The column of points[] containing the y-coordinate.
* @param firstPoint The row of the first point to be transformed.
* @param nbrPoints The number of points to be transformed.
*/
public void transform(double[][] points, int xColumn, int yColumn,
int firstPoint, int nbrPoints) {
final int lastPoint = firstPoint + nbrPoints;
double[] p = new double [2];
for (int i = firstPoint; i < lastPoint; i++) {
p[0] = points[i][xColumn];
p[1] = points[i][yColumn];
double[] p_transformed = this.transform(p);
points[i][xColumn] = p_transformed[0];
points[i][yColumn] = p_transformed[1];
}
}
/**
* Transform an array of points from the coordinate system of the source set
* to the coordinate system of the destination set.
* X-coordinates are stored in the first column, y-coordinates in the second
* column of points[][].
* The transformed points overwrite the original values in the points[] array.
* @param points The points to be transformed.
*/
public final void transform(double[][] points) {
this.transform(points, 0, 1);
}
public void transform(double[] coords, int nbrPts) {
double [] pt = new double [2];
for (int i = 0; i < nbrPts; ++i) {
pt[0] = coords[i*2];
pt[1] = coords[i*2+1];
pt = transform(pt);
coords[i*2] = pt[0];
coords[i*2+1] = pt[1];
}
}
public java.awt.geom.GeneralPath transform(java.awt.geom.PathIterator pi) {
double [] coords = new double [6];
int segmentType;
java.awt.geom.GeneralPath newGeneralPath = new java.awt.geom.GeneralPath();
while (pi.isDone() == false) {
segmentType = pi.currentSegment(coords);
switch (segmentType) {
case java.awt.geom.PathIterator.SEG_CLOSE:
newGeneralPath.closePath();
break;
case java.awt.geom.PathIterator.SEG_LINETO:
transform(coords, 1);
newGeneralPath.lineTo((float)coords[0], (float)coords[1]);
break;
case java.awt.geom.PathIterator.SEG_MOVETO:
transform(coords, 1);
newGeneralPath.moveTo((float)coords[0], (float)coords[1]);
break;
case java.awt.geom.PathIterator.SEG_QUADTO:
transform(coords, 2);
newGeneralPath.quadTo((float)coords[0], (float)coords[1],
(float)coords[2], (float)coords[3]);
break;
case java.awt.geom.PathIterator.SEG_CUBICTO:
transform(coords, 3);
newGeneralPath.curveTo((float)coords[0], (float)coords[1],
(float)coords[2], (float)coords[3],
(float)coords[4], (float)coords[5]);
break;
default:
throw new IllegalArgumentException();
}
// move to next segment
pi.next();
}
return newGeneralPath;
}
public ika.geo.GeoPath transform(ika.geo.GeoPath geoPath) {
ika.geo.GeoPath newGeoPath = new ika.geo.GeoPath();
java.awt.geom.GeneralPath transformedPath = this.transform(geoPath.toPathIterator(null));
newGeoPath.append(transformedPath, false);
newGeoPath.setVectorSymbol((VectorSymbol)geoPath.getVectorSymbol().clone());
return newGeoPath;
}
public GeoSet transform(GeoSet geoSet) {
if (geoSet == null)
throw new IllegalArgumentException();
GeoSet newGeoSet = new GeoSet();
final int nbrGeoObjects = geoSet.getNumberOfChildren();
for (int i = 0; i < nbrGeoObjects; ++i) {
GeoObject geoObject = geoSet.getGeoObject(i);
GeoObject transformedGeoObj = null;
if (geoObject instanceof GeoSet)
transformedGeoObj = this.transform((GeoSet)geoObject);
else if (geoObject instanceof GeoPath)
transformedGeoObj = this.transform((GeoPath)geoObject);
else if (geoObject instanceof GeoPoint)
transformedGeoObj = this.transform((GeoPoint)geoObject);
newGeoSet.add(transformedGeoObj);
}
return newGeoSet;
}
public GeoPoint transform(GeoPoint geoPoint) {
if (geoPoint == null)
throw new IllegalArgumentException();
double point [] = {geoPoint.getX(), geoPoint.getY()};
this.transform(point, 1);
GeoPoint newGeoPoint= new GeoPoint(point[0], point[1]);
return newGeoPoint;
}
/**
* Utility that sums the square values of a vector.
* @param v an array with a single column
* @return The sum of the square values of the elements of vector v.
*/
protected final static double vTv(double[][] v) {
if (v[0].length != 1)
throw new IllegalArgumentException();
double vTv = 0;
double a;
for (int i = 0; i < v.length; i++) {
a = v[i][0];
vTv += a*a;
}
return vTv;
}
/**
* Utility that sums the square values of a vector.
* @param v a Jama.Matrix with a single column
* @return The sum of the square values of the elements of vector v.
*/
protected final static double vTv(Jama.Matrix v) {
return Transformation.vTv(v.getArray());
}
/**
* Utility for formatting a double value. The resulting number won't have any
* decimal digits.
* @param value The value to convert to a string.
* @return The value converted to a string.
*/
protected String formatNoDecimal(double value) {
return NumberFormatter.format(value, 20, 0, 0);
}
/**
* Utility for formatting a double value. The resulting number will be quite long,
* and have some decimal digits.
* @param value The value to convert to a string.
* @return The value converted to a string.
*/
protected String formatPrecise(double value) {
return NumberFormatter.format(value, 25, 10, 0);
}
/**
* Utility for formatting a double value. The resulting number will be rather short,
* and have some decimal digits.
* @param value The value to convert to a string.
* @return The value converted to a string.
*/
protected String formatPreciseShort(double value) {
return NumberFormatter.format(value, 12, 5, 0);
}
protected double geometricRadiantToAzimuthDegree(double rad) {
double azimuth = -Math.toDegrees(rad) + 90.;
if (azimuth < 0)
azimuth += 360;
return azimuth;
}
protected String formatSigma0(double sigma0) {
java.text.DecimalFormat sigma0Formatter
= new java.text.DecimalFormat(sigma0 < 10 ? "###,###.0####" : "###,###");
StringBuffer str = new StringBuffer();
/*
str.append("Mean Error ");
str.append("\u03c3"); // "greek small letter sigma"
str.append("\u02f3"); // "modifier letter low ring"
str.append(":\t");
str.append("\u00b1"); // plus-minus sign
str.append(sigma0Formatter.format(sigma0));
str.append("m");
*/
str.append("Std. Deviation:\t");
str.append("\u00b1"); // plus-minus sign
str.append(sigma0Formatter.format(sigma0));
str.append("m");
return str.toString();
}
protected String formatStandardErrorOfPosition(double stdErrPos) {
DecimalFormat formatter
= new DecimalFormat(stdErrPos < 1 ? "###,###.0####" : "###,###");
StringBuffer str = new StringBuffer();
str.append("Mean Pos. Error:\t");
str.append("\u00b1"); // plus-minus sign
str.append(formatter.format(stdErrPos));
str.append("m");
return str.toString();
}
protected String formatRotation(String label, double rad) {
java.text.DecimalFormat angleFormatter = new java.text.DecimalFormat(
"###");
String suffix = "[cw]";
double deg = Math.toDegrees(rad);
if (deg < 0)
deg += 360;
if (deg > 180) {
deg = 360 - deg;
suffix = "[ccw]";
}
StringBuffer str = new StringBuffer();
str.append(label);
str.append(":\t");
str.append(angleFormatter.format(deg));
str.append("\u00b0 "); // degree sign
str.append(suffix);
return str.toString();
}
}