/*
* 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.clustering.functions;
import java.awt.geom.Point2D;
import at.tuwien.ifs.somtoolbox.clustering.Cluster;
import at.tuwien.ifs.somtoolbox.clustering.DistanceFunctionType;
import at.tuwien.ifs.somtoolbox.structures.ComponentLine2D;
public class ComponentLine2DDistance implements ClusterElementFunctions<ComponentLine2D> {
private DistanceFunctionType distanceType;
public ComponentLine2DDistance(DistanceFunctionType distanceType) {
this.distanceType = distanceType;
}
public static ClusterElementFunctions<ComponentLine2D> getEuclidean() {
return new ComponentLine2DDistance(DistanceFunctionType.Euclidean);
}
@Override
/* Computes the distance between two lines, using the given distance function. */
public double distance(ComponentLine2D line1, ComponentLine2D line2) {
return distance(line1.getPoints(), line2.getPoints());
}
public double distance(Point2D[] points1, Point2D[] points2) {
switch (distanceType) {
case Euclidean:
return euclideanLineDistance(points1, points2);
case MinEuclidean:
return minimumEuclideanLineDistance(points1, points2);
case Edit:
return editLineDistance(points1, points2);
case Area:
return areaLineDistance(points1, points2);
case EuclideanDiffNrOfStops:
return euclideanLineDistanceDifferentNumberOfStops(points1, points2);
default:
return -1; // TODO: maybe throw an exception?
}
}
/** Computes the distance between two lines in terms of absolute distances between single segments. */
public static double euclideanLineDistance(Point2D[] line1, Point2D[] line2) {
double distance1 = 0;
double distance2 = 0;
for (int i = 0; i < line2.length; i++) {
distance1 += line1[i].distance(line2[i]);
distance2 += line1[i].distance(line2[line2.length - i - 1]);
}
return Math.min(distance1, distance2);
}
private static Point2D getNextValidPoint(Point2D[] line, int index) {
for (int i = index; i < line.length; i++) {
if (i == line.length - 1 || line[i].equals(line[i + 1])) {
return line[i];
}
}
return null;
}
private static Point2D getPreviousValidPoint(Point2D[] line, int index) {
for (int i = index; i >= 0; i--) {
if (i == 0 || line[i].equals(line[i - 1])) {
return line[i];
}
}
return null;
}
public static double euclideanLineDistanceDifferentNumberOfStops(Point2D[] line1, Point2D[] line2) {
double distance1 = 0;
double distance2 = 0;
for (int i = 0; i < line2.length; i++) {
final int endIndex = line2.length - i - 1;
Point2D previousValidPoint1 = getPreviousValidPoint(line2, i);
Point2D nextValidPoint1 = getNextValidPoint(line2, i);
System.out.println("nvp " + nextValidPoint1);
distance1 += Math.min(line1[i].distance(previousValidPoint1), line1[i].distance(nextValidPoint1));
Point2D previousValidPoint = getPreviousValidPoint(line1, endIndex);
Point2D nextValidPoint = getNextValidPoint(line1, endIndex);
distance2 += Math.min(line2[endIndex].distance(previousValidPoint),
line2[endIndex].distance(nextValidPoint));
}
return Math.min(distance1, distance2);
}
/** Computes the distance between two lines in terms of distances between single segments. */
public static double minimumEuclideanLineDistance(Point2D[] line1, Point2D[] line2) {
double distance1 = 0;
double distance2 = 0;
for (int i = 0; i < line2.length; i++) {
double minDistance1 = Double.MAX_VALUE;
double minDistance2 = Double.MAX_VALUE;
for (Point2D element : line2) {
double tempDistance1 = line1[i].distance(line2[i]);
if (tempDistance1 < minDistance1) {
minDistance1 = tempDistance1;
}
double tempDistance2 = line1[i].distance(line2[line2.length - i - 1]);
if (tempDistance2 < minDistance2) {
minDistance2 = tempDistance2;
}
}
distance1 += minDistance1;
distance2 += minDistance2;
}
return Math.min(distance1, distance2);
}
/**
* Computes the distance between two lines in terms of edit operations necessary to move one line onto the other.
* This basically computes distances for beginning and end points of single lines and penalises non parallel lines .
*/
public static double editLineDistance(Point2D[] line1, Point2D[] line2) {
double dist = 0;
double parallelFactor = .5;
for (int i = 1; i < line2.length - 1; i++) {
double dn = line1[i].distance(line2[i]) + line1[i + 1].distance(line2[i + 1]);
double dr = line1[i].distance(line2[i + 1]) + line1[i + 1].distance(line2[i]);
if (linesParallel(line1[i], line1[i + 1], line2[i], line2[i + 1])) {
dn = dn * parallelFactor;
dr = dr * parallelFactor;
}
dist += dn + dr;
}
return dist;
}
/** Computes if two lines are parallel to each other, by comparing their slopes. */
public static boolean linesParallel(Point2D line1Begin, Point2D line1End, Point2D line2Begin, Point2D line2End) {
return curveSlope(line1Begin, line1End) == curveSlope(line2Begin, line2End)
|| curveSlope(line1Begin, line1End) == curveSlope(line2End, line2Begin);
}
/** Computes the slope of a line/curve between the given points. */
public static double curveSlope(Point2D start, Point2D end) {
return (end.getX() - start.getX()) / (end.getY() - start.getY());
}
/** Computes the distance between two lines by computing the area stretching between them. */
public static double areaLineDistance(Point2D[] line1, Point2D[] line2) {
// FIXME this can't be it
double dist = 0;
return dist;
}
@Override
public ComponentLine2D meanObject(Cluster<? extends ComponentLine2D> elements) {
if (elements.size() == 1) {
return elements.get(0);
}
Point2D[] mean = new Point2D[elements.get(0).getLength()];
for (int i = 0; i < mean.length; i++) {
double x = 0;
double y = 0;
for (int j = 0; j < elements.size(); j++) {
ComponentLine2D array = elements.get(j);
x += array.get(i).getX();
y += array.get(i).getY();
}
x = x / elements.size();
y = y / elements.size();
mean[i] = new Point2D.Double(x, y);
}
return new ComponentLine2D(mean);
}
public int getIndexOfLineClosestToMean(Cluster<? extends ComponentLine2D> elements) {
double minDist = Double.POSITIVE_INFINITY;
int minIndex = 0;
ComponentLine2D meanObject = meanObject(elements);
for (int k = 0; k < elements.size(); k++) {
double distance = distance(meanObject, elements.get(k));
if (distance <= minDist) {
minDist = distance;
minIndex = k;
}
}
return minIndex;
}
@Override
public String toString(Cluster<? extends ComponentLine2D> elements) {
StringBuilder sb = new StringBuilder();
for (Point2D p : meanObject(elements).getPoints()) {
if (sb.length() > 0) {
sb.append(" => ");
}
sb.append(DF.format(p.getX())).append(" / ").append(DF.format(p.getY()));
}
return getClass().getSimpleName() + ", lines: " + elements.size() + ", mean line: " + sb;
}
}