Server Side Clustering Class in C#
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
using SafeRoutes.Infrastructure.Storage.Domain; | |
namespace SafeRoutes.Infrastructure.Services | |
{ | |
/// <summary> | |
/// Service that clusters ProjectPoints | |
/// </summary> | |
/// <remarks>could be generalized via interfaces</remarks> | |
public static class PointClusterService | |
{ | |
/// <remarks>Originally From James O'Brien's Clustering Sample</remarks> | |
public static List<ProjectPoint> Cluster(List<ProjectPoint> pins, double resolution) | |
{ | |
//sort pins - must be ordered correctly by x,y | |
pins.Sort(); | |
List<ProjectPoint> clusteredPoints = new List<ProjectPoint>(); | |
for (int index = 0; index < pins.Count; index++) | |
{ | |
if (!pins[index].c) //skip already clustered pins | |
{ | |
ProjectPoint currentClusterPin = new ProjectPoint(); | |
//create our cluster object and add the first pin | |
currentClusterPin.AddPoint(pins[index]); | |
//the pin will represent this first point... | |
currentClusterPin.pid = pins[index].pid; | |
currentClusterPin.Amount = pins[index].Amount; | |
currentClusterPin.Name = pins[index].Name; | |
currentClusterPin.Src = pins[index].Src; | |
currentClusterPin.Year = pins[index].Year; | |
currentClusterPin.cs = pins[index].cs; | |
currentClusterPin.nsc = pins[index].nsc; | |
currentClusterPin.Location = pins[index].Location; | |
//Need to know what icon to show | |
if (pins[index].Src.ToLower() == "statewide") | |
{ | |
currentClusterPin.st = true; | |
} | |
//If any pin the the cluster is positive,the entire cluster needs to know | |
if (pins[index].Src.ToLower() == "district") | |
{ | |
currentClusterPin.sd =true; | |
} | |
//look backwards in the list for any points within the range that are not already grouped, | |
//as the points are in order we exit as soon as it exceeds the range. | |
PointClusterService.AddPinsWithinRange(pins, index, -1, currentClusterPin, resolution); | |
//look forwards in the list for any points within the range, again we short out. | |
PointClusterService.AddPinsWithinRange(pins, index, 1, currentClusterPin, resolution); | |
currentClusterPin.ico = GetPointIcon(currentClusterPin); | |
currentClusterPin.SortCluster(); | |
clusteredPoints.Add(currentClusterPin); | |
} | |
} | |
return clusteredPoints; | |
} | |
public static string GetPointIcon(ProjectPoint pt) | |
{ | |
//assume it's just a school | |
string icoName = "sa"; | |
//check if the st flag has been set (i.e. this is a state-wide award) | |
if (pt.st) | |
{ | |
//state award trumps everything... | |
icoName = "sta"; | |
} | |
else if(pt.sd) | |
{ | |
icoName = "sda"; | |
} | |
//if it's clustered... | |
if (pt.c) { icoName += "-c"; } | |
return icoName; | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
/// <param name="pins"></param> | |
/// <param name="index"></param> | |
/// <param name="direction"></param> | |
/// <param name="currentClusterPin"></param> | |
/// <param name="zoomLevel"></param> | |
/// <remarks>From James O'Brien's Clustering Sample</remarks> | |
private static void AddPinsWithinRange(List<ProjectPoint> pins, int index, int direction, ProjectPoint currentClusterPin, double resolution) | |
{ | |
//Cluster width & heigth are in pixels. So any point within 20 pixels at the zoom level will be clustered. | |
int clusterwidth = 22; //Cluster region width, all pin within this area are clustered | |
bool finished = false; | |
int searchindex; | |
searchindex = index + direction; | |
while (!finished) | |
{ | |
if (searchindex >= pins.Count || searchindex < 0) | |
{ | |
finished = true; | |
} | |
else | |
{ | |
if (!pins[searchindex].c) | |
{ | |
//find distance between two points at specified indexes | |
if (Math.Abs(pins[searchindex].Location.X - pins[index].Location.X) / resolution < clusterwidth) //within the same x range | |
{ | |
if (Math.Abs(pins[searchindex].Location.Y - pins[index].Location.Y) / resolution < clusterwidth) //within the same y range = cluster needed | |
{ | |
//add to cluster | |
currentClusterPin.AddPoint(pins[searchindex]); | |
//this point represents a cluster... | |
currentClusterPin.c = true; | |
//================================================================================= | |
// Need to update the parent when we find a higher ranking point in the cluster | |
//basically transfer all the attributes "up" | |
//================================================================================= | |
if (currentClusterPin.Src.ToLower() == "school" && pins[searchindex].Src.ToLower() == "district") | |
{ | |
//currently it's a school, and the new pin is a district... copy | |
currentClusterPin.sd = true; | |
currentClusterPin.pid = pins[searchindex].pid; | |
currentClusterPin.Amount = pins[searchindex].Amount; | |
currentClusterPin.Name = pins[searchindex].Name; | |
currentClusterPin.Src = pins[searchindex].Src; | |
currentClusterPin.Year = pins[searchindex].Year; | |
currentClusterPin.cs = pins[searchindex].cs; | |
currentClusterPin.nsc = pins[searchindex].nsc; | |
} | |
if (currentClusterPin.Src.ToLower() == "school" && pins[searchindex].Src.ToLower() == "statewide") | |
{ | |
//clone up | |
currentClusterPin.st = true; | |
currentClusterPin.pid = pins[searchindex].pid; | |
currentClusterPin.Amount = pins[searchindex].Amount; | |
currentClusterPin.Name = pins[searchindex].Name; | |
currentClusterPin.Src = pins[searchindex].Src; | |
currentClusterPin.Year = pins[searchindex].Year; | |
currentClusterPin.cs = pins[searchindex].cs; | |
currentClusterPin.nsc = pins[searchindex].nsc; | |
} | |
if (currentClusterPin.Src.ToLower() == "district" && pins[searchindex].Src.ToLower() == "statewide") | |
{ | |
//clone up | |
currentClusterPin.st = true; | |
currentClusterPin.pid = pins[searchindex].pid; | |
currentClusterPin.Amount = pins[searchindex].Amount; | |
currentClusterPin.Name = pins[searchindex].Name; | |
currentClusterPin.Src = pins[searchindex].Src; | |
currentClusterPin.Year = pins[searchindex].Year; | |
currentClusterPin.cs = pins[searchindex].cs; | |
currentClusterPin.nsc = pins[searchindex].nsc; | |
} | |
//stop any further clustering actions on the pin we are comparing | |
//because it's already added to a cluster | |
pins[searchindex].c = true; | |
} | |
} | |
else | |
{ | |
finished = true; | |
} | |
} | |
searchindex += direction; | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment