/// <summary> /// Get the minimum distance between two clusters /// </summary> /// <param name="a">Cluster A</param> /// <param name="b">Cluster B</param> /// <returns>Minimum straight-line distance between the two clusters of points</returns> public static double GetMinDistance(Cluster a, Cluster b) { // Compute all distances List<double> distances = new List<double>(); foreach (Point3D pointA in a.points) { foreach (Point3D pointB in b.points) { distances.Add(pointA.DistanceTo(pointB)); } } return distances.Min(); }
private void WorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs) { // Prepare the output file path string outputPath = Path.GetDirectoryName(this.InputFile); string outputName = Path.GetFileNameWithoutExtension(this.InputFile) + ".cluster"; string outputFile = Path.Combine(outputPath, outputName); // Load the points List<Point3D> rawPoints = LoadPointsFromFile(this.InputFile); // Collapse the points to the central Z var pointZValues = from x in rawPoints select x.Z; double centralZ = (pointZValues.Max() + pointZValues.Min())/2.0; List<Point3D> freePoints = (from x in rawPoints select new Point3D(x.X, x.Y, centralZ)).ToList(); // Begin the clustering! List<Cluster> clusters = (from x in freePoints select new Cluster(x)).ToList(); bool action = true; int iteration = 0; int merges = 0; double toleranceInMeters = this.Tolerance/100.0; while (action) { action = false; // Iterate through all of the clusters, checking each one against all of the other clusters to see // if any valid merges can be made. for (int i = 0; i < clusters.Count; i++) { // We start with an empty list of valid merges. As we go through the clusters will we check each one for // clusters that are below the minimum tolerance distance and add them to the list of valid merges. When // we have finished going through the clusters we will see if there are any valid merges, and if there are // we will perform the merges and construct a new list of clusters, beginning the process again. List<int> validMerges = new List<int>(); for (int j = 0; j < clusters.Count; j++) { if (i == j) continue; if (Cluster.GetMinDistance(clusters[i], clusters[j]) < toleranceInMeters) { validMerges.Add(j); } } // If there are valid merges we must now perform them. if (validMerges.Any()) { merges += validMerges.Count; validMerges.Add(i); var clustersToMerge = from x in validMerges select clusters[x]; Cluster merged = new Cluster(clustersToMerge); // Construct the new list of clusters without any of the ones that were merged List<Cluster> newClusterList = new List<Cluster> {merged}; // Now add every cluster after i for (int j = i; j < clusters.Count; j++) { if (!validMerges.Contains(j)) newClusterList.Add(clusters[j]); } // Now add every cluster before i, which presumably has no merges for (int j = 0; j < i; j++) { if (!validMerges.Contains(j)) newClusterList.Add(clusters[j]); } clusters = newClusterList; action = true; break; } } // Count the total points int totalPoints = 0; foreach (var cluster in clusters) { totalPoints += cluster.Count; } iteration++; string updateText = string.Format("Clusters: {0}, Merges: {1}, Total Points: {3}, Iteration {2}", clusters.Count, merges, iteration, totalPoints); WorkerOnProgressChanged(sender, new ProgressChangedEventArgs(0, updateText)); } File.WriteAllText(outputFile, JsonConvert.SerializeObject(clusters, Formatting.Indented)); }
/// <summary> /// Create a cluster from the merge of two clusters /// </summary> /// <param name="a"></param> /// <param name="b"></param> public Cluster(Cluster a, Cluster b) { this.points = new List<Point3D>(); this.points.AddRange(a.points); this.points.AddRange(b.points); }