Skip to content

Instantly share code, notes, and snippets.

@dbouwman
Created March 24, 2012 21:26
Show Gist options
  • Save dbouwman/2188210 to your computer and use it in GitHub Desktop.
Save dbouwman/2188210 to your computer and use it in GitHub Desktop.
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