/*
* Copyright 2004-2010 Information & Software Engineering Group (188/1)
* Institute of Software Technology and Interactive Systems
* Vienna University of Technology, Austria
*
* 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.ifs.tuwien.ac.at/dm/somtoolbox/license.html
*
* 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 at.tuwien.ifs.somtoolbox.visualization;
import java.util.ArrayList;
import java.util.logging.Logger;
import org.apache.commons.lang.ArrayUtils;
import at.tuwien.ifs.somtoolbox.SOMToolboxException;
import at.tuwien.ifs.somtoolbox.clustering.DistanceFunctionType;
import at.tuwien.ifs.somtoolbox.clustering.functions.ComponentLine3DDistance;
import at.tuwien.ifs.somtoolbox.layers.metrics.AbstractMetric;
import at.tuwien.ifs.somtoolbox.util.Point3d;
import at.tuwien.ifs.somtoolbox.util.StdErrProgressWriter;
public class Snapper3D extends Snapper {
private Point3d[] grid;
private static double gridSize = 0d;
/*
* public static double[][] convertPointArrayToDoubleArrayArray(Point2D[] centres){ double[][] doubleCentres = new double[centres.length][2]; for
* (int i = 0; i < centres.length; i++) { doubleCentres[i][1] = centres[i].getX(); doubleCentres[i][1] = centres[i].getY(); } return
* doubleCentres; }
*/
// public Snapper2D(AbstractMetric distanceFunction, LineDistanceFunction lineDistanceFunction, int xSize, int
// ySize) {
public Snapper3D(AbstractMetric distanceFunction, DistanceFunctionType lineDistanceFunction) {
this.distanceFunction = distanceFunction;
this.lineDistanceFunction = lineDistanceFunction;
}
public Snapper3D(double gridSize, AbstractMetric distanceFunction, DistanceFunctionType lineDistanceFunction) {
this.distanceFunction = distanceFunction;
this.lineDistanceFunction = lineDistanceFunction;
}
public static double[] normalise(double[] vec) {
double maxValue = 0d;
double minValue = 0d;
// we don't normalise vectors of length 1 (I'm sick of having 2.0 at every position)
if (vec.length == 1) {
return vec;
}
for (int i = 0; i < vec.length; i++) {
double currentValue = vec[i];
if (i == 0) {
maxValue = currentValue;
minValue = currentValue;
}
if (currentValue > maxValue) {
maxValue = currentValue;
}
if (currentValue < minValue) {
minValue = currentValue;
}
}
for (int i = 0; i < vec.length; i++) {
if (maxValue == 0) {
vec[i] = 0;
} else {
vec[i] = (vec[i] + minValue) / maxValue;
}
}
return vec;
}
public Point3d[] doSnapping(double[][] centres) throws SOMToolboxException {
Point3d[] line = new Point3d[centres.length];
for (int i = 0; i < centres.length; i++) {
line[i] = new Point3d(centres[i][0], centres[i][1], centres[i][2]);
}
return doSnapping(line);
}
public Point3d[] doSnapping(Point3d[] line) throws SOMToolboxException {
// int numberOfBins = binCentres[0].length;
Logger.getLogger("at.tuwien.ifs.somtoolbox").info("Starting 3d snapping process");
Point3d[] snappedBinCentres = new Point3d[line.length];
// for (int i_components = 0; i_components < centres.length; i_components++) {
// FIXME: kind of static
// Point3d[] x = centres[i_components];
// snappedBinCentres[i_components] = snap(centres[i_components], grid.length, 3);
// }
// FIXME
// Point3d[] ar = new Point3d[centres.length];
// for (int i = 0; i < centres.length; i++) {
// ar[i] = new Point3d(centres[i][0], centres[i][1], centres[i][2]);
// }
System.out.println("start snapping for : " + ArrayUtils.toString(line));
snappedBinCentres = snap(line);
// Point3d[][] snappedCentres = snappedBinCentres;
// Hashtable<Point3d, int[]> outgoingLineTable = new Hashtable<Point3d, int[]>(); // find counts for parallel
// lines
// for (int l = 0; l < snappedCentres.length; l++) { // for each component
// Point3d[] snappedLine = snappedCentres[l];
// }
return snappedBinCentres;
}
/**
* returns the direction between two nodes based on the following scheme: 0 7 left up up right up 1 \ | / 6 left - *
* - right 6 / | \ 5 left down down right down 3 4
*
* @param current current node
* @param next next node to go to
* @return dir from current to next
*/
private int getDirection(Point3d current, Point3d next) {
// this handles the case when this annoying function is called with identical parameters
int dir = -1;
// up
if (current.x == next.x && current.y > next.y) {
dir = 0;
}
// right up
if (current.x < next.x && current.y > next.y) {
dir = 1;
}
// right
if (current.x < next.x && current.y == next.y) {
dir = 2;
}
// right down
if (current.x < next.x && current.y < next.y) {
dir = 3;
}
// down
if (current.x == next.x && current.y < next.y) {
dir = 4;
}
// left down
if (current.x > next.x && current.y < next.y) {
dir = 5;
}
// left
if (current.x > next.x && current.y == next.y) {
dir = 6;
}
// left up
if (current.x > next.x && current.y > next.y) {
dir = 7;
}
return dir;
}
// TODO make private again
public Point3d[] createGrid(int xSize, int ySize, int zSize) {
// createGrid(xSize, ySize, zSize, "", "", "");
// int xSize = gsom.getLayer().getXSize();
// int ySize = gsom.getLayer().getYSize();
// ]
if (gridSize != 0) {
// xSize = (int) gridSize;
// ySize = (int) gridSize;
// zSize = (int) gridSize;
}
// FIXME: static ...
// int xSize = new Double(gridSize).intValue();
// int ySize = new Double(gridSize).intValue();
// int zSize = new Double(gridSize).intValue();
// if (grid == null) {
// init the data structures for neighbourhood lookup
grid = new Point3d[xSize * ySize * zSize];
int index = 0;
for (int x = 0; x < xSize; x++) {
for (int y = 0; y < ySize; y++) {
// allSOMCoordinates[x * ySize + y] = new Point2D.Double(x, y);
for (int z = 0; z < zSize; z++) {
// System.out.println(x + " / " + y + " / " + z + " | " + index);// + (x + y + z) + "| " + (x*y*z) +
// " | index: " + (x * xSize
// + y * ySize + z));
Point3d p = new Point3d(x, y, z);
// System.out.println(p);
grid[index++] = p;
}
}
}
/*
* gridSumsXY = new double[grid.length]; gridDiffsXY = new double[grid.length]; gridSumsYZ = new double[grid.length]; gridDiffsYZ = new
* double[grid.length]; gridSumsXZ = new double[grid.length]; gridDiffsXZ = new double[grid.length]; for (int i = 0; i < gridSumsXY.length;
* i++) { gridSumsXY[i] = grid[i].x + grid[i].y; // + grid[i].y; gridSumsYZ[i] = grid[i].y + grid[i].z; // + grid[i].y; gridSumsXZ[i] =
* grid[i].x + grid[i].z; // + grid[i].y; gridDiffsXY[i] = grid[i].x - grid[i].y; // - grid[i].y; gridDiffsYZ[i] = grid[i].y - grid[i].z; // -
* grid[i].y; gridDiffsXZ[i] = grid[i].x - grid[i].z; // + grid[i].y; }
*/
// }
// System.out.println(ArrayUtils.toString(grid));
// System.out.println(ArrayUtils.toString(gridSums));
// System.out.println(ArrayUtils.toString(gridDiffs));
// System.out.println(grid.length);
// System.out.println(grid[0][0].length);
// System.out.println(ArrayUtils.toString(grid));
return grid;
}
public double[][] getGrid() {
return convert(grid);
}
public double[][] convert(Point3d[] array) {
double[][] data = new double[array.length][3];
for (int i = 0; i < array.length; i++) {
data[i] = new double[] { array[i].x, array[i].y, array[i].z };
}
return data;
}
/**
* Returns a snapped line of the given line. Snapping the metro lines means to find a line as similar as possible to
* the given line, which has all bin centres in the unit centres, and line segments are connected in multiples of
* 45° degree angles to each other.<br>
* TODO: Consider disallowing 135° / 315° as too sharp turns. private Point2D[] snap(Point2D[] line, int xSize, int
* ySize) { // Snapping process // 1. For each bin centre, find the 4 neighbouring Unit locations, thus resulting in
* bins * 4 points // 2. For each point: consider the point as fixed, and find a line that is correctly snapped
* (only 45°) angles and as near as possible to the // original line // 3. From the resulting bins * 4 lines, chose
* the one closest to the original line ArrayList<Point2D[]> allSnappedLines = new ArrayList<Point2D[]>(); for (int
* i = 0; i < line.length; i++) { Point2D[] neighbouringPoints = getNeighbouringUnits(line[i]); for (int j = 0; j <
* neighbouringPoints.length; j++) { // find the snapped points forward and backwards from the current bin point //
* this means we will have lines e.g. as follows (for 6 bins, and i == 3) // lineSegmentForward = (0/0 0/0 0/0 0/0
* x5/y5 x6/y6) // lineSegmentBackward = (x1/y1 x2/y2, x3/y3 0/0 0/0 0/0) Point2D[] lineSegmentForward =
* snapPoint(neighbouringPoints[j], line, i + 1, +1, xSize, ySize, centres[0].length); Point2D[] lineSegmentBackward
* = snapPoint(neighbouringPoints[j], line, i - 1, -1, xSize, ySize, centres[0].length); // then merge them to one
* line, and set the fixed point Point2D[] mergedLine = new Point2D[lineSegmentForward.length]; for (int k = 0; k <
* lineSegmentBackward.length; k++) { mergedLine[k] = new Point2D.Double(lineSegmentForward[k].getX() +
* lineSegmentBackward[k].getX(), lineSegmentForward[k].getY() + lineSegmentBackward[k].getY()); } mergedLine[i] =
* neighbouringPoints[j]; // now if that point is the same as the last one, don't add that one // for some strange
* reason this is required additionally to the condition in the recursion if ((i > 1 && (!(line[i].getX() == line[i
* - 1].getX() && line[i].getY() == line[i - 1].getY())))) { allSnappedLines.add(mergedLine); } } } // find the
* closest snapped line double minDist = Double.MAX_VALUE; Point2D[] minDistLine = null; for (int i = 0; i <
* allSnappedLines.size(); i++) { Point2D[] currentLine = (Point2D[]) allSnappedLines.get(i); double dist =
* LineDistance.lineDistance(line, currentLine, lineDistanceFunction); if (dist < minDist) { minDist = dist;
* minDistLine = currentLine; } } return minDistLine; }
*/
/**
* Returns a snapped line of the given line. Snapping the metro lines means to find a line as similar as possible to
* the given line, which has all bin centres in the unit centres, and line segments are connected in multiples of
* 45° degree angles to each other.<br>
* TODO: Consider disallowing 135° / 315° as too sharp turns.
*/
private Point3d[] snap(Point3d[] line) {
// Snapping process
// 1. For each bin centre, find the 4 neighbouring Unit locations, thus resulting in bins * 4 points
// 2. For each point: consider the point as fixed, and find a line that is correctly snapped (only 45°) angles
// and as near as possible to the
// original line
// 3. From the resulting bins * 4 lines, chose the one closest to the original line
ArrayList<Point3d[]> allSnappedLines = new ArrayList<Point3d[]>();
StdErrProgressWriter progress = new StdErrProgressWriter(line.length * 8, "calc snap ", line.length * 8 / 100);
for (int i = 0; i < line.length; i++) {
System.out.println("\tsnapping point: " + line[i]);
Point3d[] neighbouringPoints = getNeighbouringUnits(line[i]);
for (Point3d neighbouringPoint : neighbouringPoints) {
// find the snapped points forward and backwards from the current bin point
// this means we will have lines e.g. as follows (for 6 bins, and i == 3)
// lineSegmentForward = (0/0 0/0 0/0 0/0 x5/y5 x6/y6)
// lineSegmentBackward = (x1/y1 x2/y2, x3/y3 0/0 0/0 0/0)
Point3d[] lineSegmentForward = snapPoint(neighbouringPoint, line, i + 1, +1, line.length);
Point3d[] lineSegmentBackward = snapPoint(neighbouringPoint, line, i - 1, -1, line.length);
// then merge them to one line, and set the fixed point
Point3d[] mergedLine = new Point3d[lineSegmentForward.length];
for (int k = 0; k < lineSegmentBackward.length; k++) {
// TODO does this have to be normalised?
mergedLine[k] = new Point3d((lineSegmentForward[k].x + lineSegmentBackward[k].x),
(lineSegmentForward[k].y + lineSegmentBackward[k].y),
(lineSegmentForward[k].z + lineSegmentBackward[k].z));
}
mergedLine[i] = neighbouringPoint;
// System.out.println("candidate: " + i + " " + ArrayUtils.toString(mergedLine[i]));
// now if that point is the same as the last one, don't add that one
// for some strange reason this is required additionally to the condition in the recursion
// FIXME is this necessary?
// FIXME was this necessary?
// if ((i > 1
// && (!(line[i].x == line[i - 1].x
// && line[i].y == line[i - 1].y
// && line[i].z == line[i - 1].z)))) {
allSnappedLines.add(mergedLine);
// System.out.println("chose that one: " + ArrayUtils.toString(mergedLine));
// }
// progress.progress();
}
}
// find the closest snapped line
double minDist = Double.MAX_VALUE;
Point3d[] minDistLine = null;
// System.out.println("found " + allSnappedLines.size() + " solutions.");
if (allSnappedLines.size() == 0) {
return line.clone();
}
for (int i = 0; i < allSnappedLines.size(); i++) {
Point3d[] currentLine = allSnappedLines.get(i);
double dist = new ComponentLine3DDistance(lineDistanceFunction).distance(line, currentLine);
// System.out.println("candidate: " + i + " " + ArrayUtils.toString(currentLine));
if (dist < minDist) {
minDist = dist;
minDistLine = currentLine;
}
}
// return allSnappedLines.get(8);
return minDistLine;
}
/**
* Snaps the next point on the line.
*
* @param startPoint the point to start from
* @param line the line to snap
* @param currentPosition the current position on the line
* @param direction forward (1) or backwards (-1)
* @param bins number of bins
* @return a snapped line
*/
private Point3d[] snapPoint(Point3d startPoint, Point3d[] line, int currentPosition, int direction, int bins) {
Point3d[] result = new Point3d[bins];
if (currentPosition == -1 && direction == -1 || currentPosition == bins && direction == 1) {
for (int i = 0; i < result.length; i++) {
result[i] = new Point3d(0d, 0d, 0d);
}
return result;
}
// System.out.println(startPoint);
// int startPointCoordinatesSumXY = (int) (startPoint.x + startPoint.y); // + startPoint.z);
// int startPointCoordinatesSumYZ = (int) (startPoint.y + startPoint.z); // + startPoint.z);
// int startPointCoordinatesSumXZ = (int) (startPoint.x + startPoint.z); // + startPoint.z);
// int startPointCoordinatesDifferenceXY = (int) (startPoint.x - startPoint.y); // - startPoint.z);
// int startPointCoordinatesDifferenceYZ = (int) (startPoint.y - startPoint.z); // - startPoint.z);
// int startPointCoordinatesDifferenceXZ = (int) (startPoint.x - startPoint.z); // - startPoint.z);
double minDistance = Double.MAX_VALUE;
Point3d closestPoint = null;
// TODO
// System.out.println(ArrayUtils.toString(line));
// System.out.println("currentposition: " + currentPosition);
// System.out.println("direction: " + direction);
// System.out.println("bins: " + bins);
for (Point3d element : grid) {
// find units that are either in the same row (x equal), same column (y equal) or are in a diagonal (sum or
// diff values equal)
// if (grid[i].x == startPoint.x || grid[i].y == startPoint.y || grid[i].z == startPoint.z
// || gridSums[i] == startPointCoordinatesSum || gridDiffs[i] == startPointCoordinatesDifference) {
/*
* if ( (grid[i].x == startPoint.x || gridSumsXY[i] == startPointCoordinatesSumXY || gridSumsXY[i] == startPointCoordinatesSumXY) // &&
* grid[i].x == startPoint.z)// && grid[i].x == startPoint.y) && (grid[i].y == startPoint.y || gridSumsYZ[i] == startPointCoordinatesSumYZ
* || gridDiffsYZ[i] == startPointCoordinatesDifferenceYZ) // && grid[i].y == startPoint.z) // && grid[i].x == grid[i].x) // && (grid[i].z
* == startPoint.z // || gridSumsXZ[i] == startPointCoordinatesSumXZ // || gridDiffsXZ[i] == startPointCoordinatesDifferenceXZ)) ) {
*/
if ((element.x == startPoint.x || element.y == startPoint.y || Math.abs(element.x - startPoint.x) == Math.abs(element.y
- startPoint.y))
&& (element.x == startPoint.x || element.z == startPoint.z || Math.abs(element.x - startPoint.x) == Math.abs(element.z
- startPoint.z))
&& (element.z == startPoint.z || element.y == startPoint.y || Math.abs(element.z - startPoint.z) == Math.abs(element.y
- startPoint.y))) {
double currentDistance = element.distance(line[currentPosition]);
if (currentDistance < minDistance) {
closestPoint = (Point3d) element.clone();
minDistance = currentDistance;
}
}
}
// compare this startpoint to the last one and check for identity and don't consider the closest but the point
// itself for further processing
// if so
if (currentPosition > 1 && line[currentPosition].x == line[currentPosition - direction].x
&& line[currentPosition].y == line[currentPosition - direction].y
&& line[currentPosition].z == line[currentPosition - direction].z) {
result = snapPoint(startPoint, line, currentPosition + direction, direction, bins);
result[currentPosition] = (Point3d) startPoint.clone();
} else {
result = snapPoint(closestPoint, line, currentPosition + direction, direction, bins);
result[currentPosition] = (Point3d) closestPoint.clone();
}
return result;
}
public Point3d[] getNeighbouringUnits(Point3d p) {
boolean debug = false;
if (debug) {
System.out.println(p);
}
Point3d leftUpperLow = new Point3d(new Double(p.x).intValue(), new Double(p.y).intValue(),
new Double(p.z).intValue());
Point3d leftUpperHigh = new Point3d(leftUpperLow.x, leftUpperLow.y, leftUpperLow.z + 1);
Point3d leftLowerLow = new Point3d(leftUpperLow.x + 1, leftUpperLow.y, leftUpperLow.z);
Point3d leftLowerHigh = new Point3d(leftUpperLow.x + 1, leftUpperLow.y, leftUpperLow.z + 1);
Point3d rightUpperLow = new Point3d(leftUpperLow.x, leftUpperLow.y + 1, leftUpperLow.z);
Point3d rightUpperHigh = new Point3d(leftUpperLow.x, leftUpperLow.y + 1, leftUpperLow.z + 1);
Point3d rightLowerLow = new Point3d(leftUpperLow.x + 1, leftUpperLow.y + 1, leftUpperLow.z);
Point3d rightLowerHigh = new Point3d(leftUpperLow.x + 1, leftUpperLow.y + 1, leftUpperLow.z + 1);
// now limit the options if any of the three input
// coordinates is an integer already
if (p.x % 1 == 0) {
leftUpperLow.x = p.x;
leftUpperHigh.x = p.x;
leftLowerLow.x = p.x;
leftLowerHigh.x = p.x;
rightUpperLow.x = p.x;
rightUpperHigh.x = p.x;
rightLowerLow.x = p.x;
rightLowerHigh.x = p.x;
}
if (p.y % 1 == 0) {
leftUpperLow.y = p.y;
leftUpperHigh.y = p.y;
leftLowerLow.y = p.y;
leftLowerHigh.y = p.y;
rightUpperLow.y = p.y;
rightUpperHigh.y = p.y;
rightLowerLow.y = p.y;
rightLowerHigh.y = p.y;
}
if (p.z % 1 == 0) {
leftUpperLow.z = p.z;
leftUpperHigh.z = p.z;
leftLowerLow.z = p.z;
leftLowerHigh.z = p.z;
rightUpperLow.z = p.z;
rightUpperHigh.z = p.z;
rightLowerLow.z = p.z;
rightLowerHigh.z = p.z;
}
// TODO remove this shit
if (debug) {
System.out.println("leftUpperLow: " + leftUpperLow);
System.out.println("leftUpperHigh: " + leftUpperHigh);
System.out.println("leftLowerLow: " + leftLowerLow);
System.out.println("leftLowerHigh: " + leftLowerHigh);
System.out.println();
System.out.println("rightUpperLow: " + rightUpperLow);
System.out.println("rightUpperHigh: " + rightUpperHigh);
System.out.println("rightLowerLow: " + rightLowerLow);
System.out.println("rightLowerHigh: " + rightLowerHigh);
}
return new Point3d[] { leftUpperLow, leftUpperHigh, leftLowerLow, leftLowerHigh, rightUpperLow, rightUpperHigh,
rightLowerLow, rightLowerHigh };
}
}