/*
* 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.awt.geom.Point2D;
import java.util.ArrayList;
import at.tuwien.ifs.somtoolbox.clustering.DistanceFunctionType;
import at.tuwien.ifs.somtoolbox.clustering.functions.ComponentLine2DDistance;
import at.tuwien.ifs.somtoolbox.layers.metrics.AbstractMetric;
public class Snapper2D extends Snapper {
private Point2D[] grid;
private double[] gridSums, gridDiffs;
public Snapper2D(AbstractMetric distanceFunction, DistanceFunctionType lineDistanceFunction, int xSize, int ySize) {
super(distanceFunction, lineDistanceFunction);
createGrid(xSize, ySize);
}
public Point2D[] createGrid(int xSize, int ySize) {
grid = new Point2D[xSize * ySize];
int index = 0;
for (int x = 0; x < xSize; x++) {
for (int y = 0; y < ySize; y++) {
Point2D.Double p = new Point2D.Double(x, y);
grid[index++] = p;
}
}
gridSums = new double[grid.length];
gridDiffs = new double[grid.length];
for (int i = 0; i < gridSums.length; i++) {
gridSums[i] = grid[i].getX() + grid[i].getY();
gridDiffs[i] = grid[i].getX() - grid[i].getY();
}
return grid;
}
/**
* 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)
* @return a snapped line
*/
private Point2D[] snapPoint(Point2D startPoint, Point2D[] line, int currentPosition, int direction) {
Point2D[] result = new Point2D[line.length];
if (currentPosition == -1 && direction == -1 || currentPosition == line.length && direction == 1) {
for (int i = 0; i < result.length; i++) {
result[i] = new Point2D.Double(0, 0);
}
return result;
}
int startPointCoordinatesSum = (int) (startPoint.getX() + startPoint.getY());
int startPointCoordinatesDifference = (int) (startPoint.getX() - startPoint.getY());
double minDistance = Double.MAX_VALUE;
Point2D closestPoint = null;
for (int i = 0; i < grid.length; i++) {
// 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].getX() == startPoint.getX() || grid[i].getY() == startPoint.getY()
|| gridSums[i] == startPointCoordinatesSum || gridDiffs[i] == startPointCoordinatesDifference) {
double currentDistance = grid[i].distance(line[currentPosition]);
if (currentDistance < minDistance) {
closestPoint = grid[i];
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].getX() == line[currentPosition - direction].getX()
&& line[currentPosition].getY() == line[currentPosition - direction].getY()) {
result = snapPoint(startPoint, line, currentPosition + direction, direction);
result[currentPosition] = startPoint;
} else {
result = snapPoint(closestPoint, line, currentPosition + direction, direction);
result[currentPosition] = closestPoint;
}
return result;
}
public Point2D[][] snap(Point2D[][] lines) {
Point2D[][] snappedLines = new Point2D[lines.length][lines[0].length];
for (int i = 0; i < lines.length; i++) {
snappedLines[i] = snap(lines[i]);
}
return snappedLines;
}
/**
* 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.
*/
public Point2D[] snap(Point2D[] 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<Point2D[]> allSnappedLines = new ArrayList<Point2D[]>();
for (int i = 0; i < line.length; i++) {
Point2D[] neighbouringPoints = getNeighbouringUnits(line[i]);
for (Point2D 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)
Point2D[] lineSegmentForward = snapPoint(neighbouringPoint, line, i + 1, +1);
Point2D[] lineSegmentBackward = snapPoint(neighbouringPoint, line, i - 1, -1);
// 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] = neighbouringPoint;
// 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 = allSnappedLines.get(i);
double dist = new ComponentLine2DDistance(lineDistanceFunction).distance(line, currentLine);
if (dist < minDist) {
minDist = dist;
minDistLine = currentLine;
}
}
return minDistLine;
}
/**
* Finds the four units around the given point.
*
* @param p point to find neighbours for.
* @return four neighbours.
*/
public Point2D[] getNeighbouringUnits(Point2D p) {
// FIXME what about setting all x values to x if x % 1 == 0?
// FIXME what about not?
Point2D leftUpper = new Point2D.Double((int) p.getX(), (int) p.getY());
Point2D rightUpper = new Point2D.Double(leftUpper.getX() + 1, leftUpper.getY());
Point2D leftLower = new Point2D.Double(leftUpper.getX(), leftUpper.getY() + 1);
Point2D rightLower = new Point2D.Double(leftUpper.getX() + 1, leftUpper.getY() + 1);
if (p.getX() % 1 == 0) {
rightUpper.setLocation(leftUpper.getX(), leftUpper.getY());
rightLower.setLocation(leftUpper.getX(), leftUpper.getY() + 1);
}
if (p.getY() % 1 == 0) {
leftLower.setLocation(leftUpper.getX(), leftUpper.getY());
rightLower.setLocation(leftUpper.getX() + 1, leftUpper.getY());
}
if (p.getX() % 1 == 0 && p.getY() % 1 == 0) {
rightLower.setLocation(leftUpper.getX(), leftUpper.getY());
}
// the order of the points returned here is the same as in the matlab version
return new Point2D[] { leftUpper, leftLower, rightUpper, rightLower };
}
}