/* * 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 at.tuwien.ifs.somtoolbox.clustering.Cluster; import at.tuwien.ifs.somtoolbox.clustering.DistanceFunctionType; import at.tuwien.ifs.somtoolbox.structures.ComponentLine3D; import at.tuwien.ifs.somtoolbox.util.Point3d; import at.tuwien.ifs.somtoolbox.util.VectorTools; public class ComponentLine3DDistance implements ClusterElementFunctions<ComponentLine3D> { private DistanceFunctionType distanceType; public ComponentLine3DDistance(DistanceFunctionType distanceType) { this.distanceType = distanceType; } public static ClusterElementFunctions<ComponentLine3D> getEuclidean() { return new ComponentLine3DDistance(DistanceFunctionType.Euclidean); } @Override /* Computes the distance between two lines, using the given distance function. */ public double distance(ComponentLine3D element1, ComponentLine3D element2) { return distance(element1.getPoints(), element2.getPoints()); } public double distance(Point3d[] points1, Point3d[] 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(Point3d[] line1, Point3d[] 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]); // distance2 = 10000; } return 1; // return distance1; // return Math.min(distance1, distance2); } private static Point3d getNextValidPoint(Point3d[] line, int index) { for (int i = index; i < line.length - 1; i++) { if (line[i].equals(line[i + 1]) || index == line.length - 1) { return line[i]; } } return null; } private static Point3d getPreviousValidPoint(Point3d[] line, int index) { for (int i = index; i >= 0; i--) { if (index == 0 || line[i].equals(line[i - 1])) { return line[i]; } } return null; } public static double euclideanLineDistanceDifferentNumberOfStops(Point3d[] line1, Point3d[] line2) { double distance1 = 0; double distance2 = 0; for (int i = 0; i < line2.length; i++) { final int endIndex = line2.length - i - 1; distance1 += Math.min(line1[i].distance(getPreviousValidPoint(line2, i)), line1[i].distance(getNextValidPoint(line2, i))); distance2 += Math.min(line2[endIndex].distance(getPreviousValidPoint(line1, endIndex)), line2[endIndex].distance(getNextValidPoint(line1, endIndex))); } return Math.min(distance1, distance2); } /** Computes the distance between two lines in terms of distances between single segments. */ public static double minimumEuclideanLineDistance(Point3d[] line1, Point3d[] 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 (Point3d 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(Point3d[] line1, Point3d[] 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 checking the cross product of their direction vectors. **/ public static boolean linesParallel(Point3d line1Begin, Point3d line1End, Point3d line2Begin, Point3d line2End) { // get direction vectors // get cross product // return true if cross product == 0 0 0 Point3d direction1 = new Point3d(line1Begin.x - line1End.x, line1Begin.y - line1End.y, line1Begin.z - line1End.z); Point3d direction2 = new Point3d(line2Begin.x - line2End.x, line2Begin.y - line2End.y, line2Begin.z - line2End.z); Point3d cp = VectorTools.crossProduct(direction1, direction2); if (cp.x == 0 && cp.y == 0 && cp.z == 0) { return true; } else { return false; } } /** Computes the distance between two lines by computing the area stretching between them. */ public static double areaLineDistance(Point3d[] line1, Point3d[] line2) { // FIXME this can't be it double dist = 0; return dist; } @Override public ComponentLine3D meanObject(Cluster<? extends ComponentLine3D> elements) { if (elements.size() == 1) { return elements.get(0); } Point3d[] mean = new Point3d[elements.get(0).getLength()]; for (int i = 0; i < mean.length; i++) { double x = 0; double y = 0; double z = 0; for (int j = 0; j < elements.size(); j++) { Point3d[] array = elements.get(j).getPoints(); x += array[i].x; y += array[i].y; z += array[i].z; } x = x / elements.size(); y = y / elements.size(); z = z / elements.size(); mean[i] = new Point3d(x, y, z); } return new ComponentLine3D(mean); } public int getIndexOfLineClosestToMean(Cluster<? extends ComponentLine3D> elements) { double minDist = Double.POSITIVE_INFINITY; int minIndex = 0; ComponentLine3D 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 ComponentLine3D> elements) { StringBuilder sb = new StringBuilder(); for (Point3d p : meanObject(elements).getPoints()) { if (sb.length() > 0) { sb.append(" => "); } sb.append(DF.format(p.x)).append(" / ").append(DF.format(p.y)).append(" / ").append(DF.format(p.z)); } return getClass().getSimpleName() + ", lines: " + elements.size() + ", mean line: " + sb; } }