/// <summary> /// clusters retrieved pins geographically /// </summary> /// <param name="pins"></param> /// <param name="zoomLevel"></param> /// <param name="clusterWidth"></param> /// <param name="clusterHeight"></param> /// <returns></returns> public List<ClusteredPin> cluster(List<ClusteredPin> pins, int zoomLevel, int clusterWidth, int clusterHeight) { //sort pins - must be ordered correctly. PinXYComparer pinComparer = new PinXYComparer(); pins.Sort(pinComparer); List<ClusteredPin> clusteredPins = new List<ClusteredPin>(); for (int index = 0; index < pins.Count; index++) { if ((!pins[index].IsClustered)) //skip already clusted pins { ClusteredPin currentClusterPin = new ClusteredPin(); //create our cluster object and add the first pin currentClusterPin.AddPin(pins[index]); pins[index].IsClustered = 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. addPinsWithinRange(pins, index, -1, currentClusterPin, zoomLevel, clusterWidth, clusterHeight); //look forwards in the list for any points within the range, again we short out. addPinsWithinRange(pins, index, 1, currentClusterPin, zoomLevel, clusterWidth, clusterHeight); clusteredPins.Add(currentClusterPin); } } return clusteredPins; }
/// <summary>Combine clustered pins to thin out map /// </summary> public void CombineClusteredPins(ClusteredPin newPin) { //This is needed to prevent alert to be considered for clustering //if (newPin.AssessmentHeaderData["Sector"].ToLower() == "alert") return; ClusterArea.IncludeInBounds(newPin.ClusterArea); if ((Count == 0)) { // AssessmentHeaderData = newPin.AssessmentHeaderData; pinType = newPin.PinType; // themeParameter = newPin.ThemeParameter; } else { //AssessmentHeaderData.Clear(); // AssessmentHeaderData["icontype"] = newPin.AssessmentHeaderData["icontype"]; pinType = "clusterpoint"; // themeParameter = newPin.ThemeParameter; } assessmentids.AddRange(newPin.assessmentids); Count = Count + newPin.Count; //Merge the two dictionaries into one dictionary //Source: http://stackoverflow.com/questions/10559367/combine-multiple-dictionaries-into-a-single-dictionary ServiceCodeToTitle = ServiceCodeToTitle.Concat(newPin.ServiceCodeToTitle).GroupBy(d => d.Key).ToDictionary(d => d.Key, d => d.First().Value); }
/// <summary> /// clusters retrieved pins geographically /// </summary> /// <param name="pins"></param> /// <param name="zoomLevel"></param> /// <param name="clusterWidth"></param> /// <param name="clusterHeight"></param> /// <returns></returns> public List <ClusteredPin> cluster(List <ClusteredPin> pins, int zoomLevel, int clusterWidth, int clusterHeight) { //sort pins - must be ordered correctly. PinXYComparer pinComparer = new PinXYComparer(); pins.Sort(pinComparer); List <ClusteredPin> clusteredPins = new List <ClusteredPin>(); for (int index = 0; index < pins.Count; index++) { if ((!pins[index].IsClustered)) //skip already clusted pins { ClusteredPin currentClusterPin = new ClusteredPin(); //create our cluster object and add the first pin currentClusterPin.AddPin(pins[index]); pins[index].IsClustered = 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. addPinsWithinRange(pins, index, -1, currentClusterPin, zoomLevel, clusterWidth, clusterHeight); //look forwards in the list for any points within the range, again we short out. addPinsWithinRange(pins, index, 1, currentClusterPin, zoomLevel, clusterWidth, clusterHeight); clusteredPins.Add(currentClusterPin); } } return(clusteredPins); }
/// <summary> /// Adds a pin to the cluster /// </summary> /// <param name="newPin">the pin to add</param> public void AddPin(ClusteredPin newPin) { //This is needed to prevent alert to be sonsidered for clustering //if (newPin.AssessmentHeaderData["Sector"].ToLower() == "alert") return; if (Loc == null) { Loc = newPin.Loc; } ClusterArea.IncludeInBounds(newPin.ClusterArea); if ((Count == 0)) { // AssessmentHeaderData = newPin.AssessmentHeaderData; pinType = newPin.PinType; //themeParameter = newPin.ThemeParameter; } else { //AssessmentHeaderData.Clear(); // AssessmentHeaderData["icontype"] = newPin.AssessmentHeaderData["icontype"]; pinType = "clusterpoint"; //themeParameter = newPin.ThemeParameter; } assessmentids.Add(newPin.assessmentids[0]); Count = Count + 1; //Merge the two dictionaries into one dictionary //Source: http://stackoverflow.com/questions/10559367/combine-multiple-dictionaries-into-a-single-dictionary //ServiceCodeToTitle = ServiceCodeToTitle.Concat(newPin.ServiceCodeToTitle).GroupBy(d => d.Key).ToDictionary(d => d.Key, d => d.First().Value); //Update counts dictionary... foreach (var key in newPin.ServiceCodeToTitle.Keys) { servCodeCounts[key] = (servCodeCounts.ContainsKey(key)) ? servCodeCounts[key] + 1 : 1; } //Update service code dictionary... // Expected format for strings: <service title> (<service count>) foreach (var key in servCodeCounts.Keys) { string titlePlusCount = ServiceCodeToTitle.ContainsKey(key) ? ServiceCodeToTitle[key] : newPin.ServiceCodeToTitle[key]; int last = titlePlusCount.LastIndexOf(')'); if (-1 != last) { titlePlusCount = titlePlusCount.Substring(0, last); } last = titlePlusCount.LastIndexOf(" ("); if (-1 != last) { titlePlusCount = titlePlusCount.Substring(0, last); } ServiceCodeToTitle[key] = titlePlusCount + " (" + servCodeCounts[key].ToString() + ")"; } }
/// <summary> /// clusteres visually /// </summary> /// <param name="clusteredpins"></param> /// <param name="zoomLevel"></param> /// <param name="minClusterDistance"></param> /// <returns></returns> private List <ClusteredPin> reduceClusterDensity(List <ClusteredPin> clusteredpins, int zoomLevel, int minClusterDistance) { PinXYComparer pinComparer = new PinXYComparer(); clusteredpins.Sort(pinComparer); List <ClusteredPin> pins = new List <ClusteredPin>(); //reset flag //Only cluster already clusteredpoints and do not cluster Alerts for (int j = 0; j < clusteredpins.Count; j++) { if (clusteredpins[j].PinType == "clusterpoint") { clusteredpins[j].IsClustered = false; } else { clusteredpins[j].IsClustered = true; pins.Add(clusteredpins[j]); } } for (int i = 0; i < clusteredpins.Count; i++) { if ((!clusteredpins[i].IsClustered)) //skip already clusted pins { ClusteredPin currentClusterPin = new ClusteredPin(); //create our cluster object and add the first pin currentClusterPin.AddPin(clusteredpins[i]); //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. addClusteredPinsWithinRange(clusteredpins, i, -1, clusteredpins[i], zoomLevel, minClusterDistance); //look forwards in the list for any points within the range, again we short out. addClusteredPinsWithinRange(clusteredpins, i, 1, clusteredpins[i], zoomLevel, minClusterDistance); clusteredpins[i].IsClustered = true; pins.Add(clusteredpins[i]); } } return(pins); }
/// <summary>Combine clustered pins to thin out map /// </summary> public void CombineClusteredPins(ClusteredPin newPin) { //This is needed to prevent alert to be considered for clustering //if (newPin.AssessmentHeaderData["Sector"].ToLower() == "alert") return; ClusterArea.IncludeInBounds(newPin.ClusterArea); if ((Count == 0)) { // AssessmentHeaderData = newPin.AssessmentHeaderData; pinType = newPin.PinType; // themeParameter = newPin.ThemeParameter; } else { //AssessmentHeaderData.Clear(); // AssessmentHeaderData["icontype"] = newPin.AssessmentHeaderData["icontype"]; pinType = "clusterpoint"; // themeParameter = newPin.ThemeParameter; } assessmentids.AddRange(newPin.assessmentids); Count = Count + newPin.Count; //Merge the two dictionaries into one dictionary //Source: http://stackoverflow.com/questions/10559367/combine-multiple-dictionaries-into-a-single-dictionary ServiceCodeToTitle = ServiceCodeToTitle.Concat(newPin.ServiceCodeToTitle).GroupBy(d => d.Key).ToDictionary(d => d.Key, d => d.First().Value); }
/// <summary> /// Adds a pin to the cluster /// </summary> /// <param name="newPin">the pin to add</param> public void AddPin(ClusteredPin newPin) { //This is needed to prevent alert to be sonsidered for clustering //if (newPin.AssessmentHeaderData["Sector"].ToLower() == "alert") return; if (Loc == null) { Loc = newPin.Loc; } ClusterArea.IncludeInBounds(newPin.ClusterArea); if ((Count == 0)) { // AssessmentHeaderData = newPin.AssessmentHeaderData; pinType = newPin.PinType; //themeParameter = newPin.ThemeParameter; } else { //AssessmentHeaderData.Clear(); // AssessmentHeaderData["icontype"] = newPin.AssessmentHeaderData["icontype"]; pinType = "clusterpoint"; //themeParameter = newPin.ThemeParameter; } assessmentids.Add(newPin.assessmentids[0]); Count = Count + 1; //Merge the two dictionaries into one dictionary //Source: http://stackoverflow.com/questions/10559367/combine-multiple-dictionaries-into-a-single-dictionary //ServiceCodeToTitle = ServiceCodeToTitle.Concat(newPin.ServiceCodeToTitle).GroupBy(d => d.Key).ToDictionary(d => d.Key, d => d.First().Value); //Update counts dictionary... foreach( var key in newPin.ServiceCodeToTitle.Keys) { servCodeCounts[key] = (servCodeCounts.ContainsKey(key)) ? servCodeCounts[key] + 1 : 1; } //Update service code dictionary... // Expected format for strings: <service title> (<service count>) foreach( var key in servCodeCounts.Keys) { string titlePlusCount = ServiceCodeToTitle.ContainsKey(key) ? ServiceCodeToTitle[key] : newPin.ServiceCodeToTitle[key]; int last = titlePlusCount.LastIndexOf(')'); if (-1 != last) { titlePlusCount = titlePlusCount.Substring(0, last); } last = titlePlusCount.LastIndexOf(" ("); if (-1 != last) { titlePlusCount = titlePlusCount.Substring(0, last); } ServiceCodeToTitle[key] = titlePlusCount + " (" + servCodeCounts[key].ToString() + ")"; } }
/// <summary> /// clusteres visually /// </summary> /// <param name="clusteredpins"></param> /// <param name="zoomLevel"></param> /// <param name="minClusterDistance"></param> /// <returns></returns> private List<ClusteredPin> reduceClusterDensity(List<ClusteredPin> clusteredpins, int zoomLevel, int minClusterDistance) { PinXYComparer pinComparer = new PinXYComparer(); clusteredpins.Sort(pinComparer); List<ClusteredPin> pins = new List<ClusteredPin>(); //reset flag //Only cluster already clusteredpoints and do not cluster Alerts for (int j = 0; j < clusteredpins.Count; j++) { if (clusteredpins[j].PinType == "clusterpoint") { clusteredpins[j].IsClustered = false; } else { clusteredpins[j].IsClustered = true; pins.Add(clusteredpins[j]); } } for (int i = 0; i < clusteredpins.Count; i++) { if ((!clusteredpins[i].IsClustered)) //skip already clusted pins { ClusteredPin currentClusterPin = new ClusteredPin(); //create our cluster object and add the first pin currentClusterPin.AddPin(clusteredpins[i]); //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. addClusteredPinsWithinRange(clusteredpins, i, -1, clusteredpins[i], zoomLevel, minClusterDistance); //look forwards in the list for any points within the range, again we short out. addClusteredPinsWithinRange(clusteredpins, i, 1, clusteredpins[i], zoomLevel, minClusterDistance); clusteredpins[i].IsClustered = true; pins.Add(clusteredpins[i]); } } return pins; }
/// <summary> /// Adds pins to nearby cluster for theme /// </summary> /// <param name="pins"></param> /// <param name="index"></param> /// <param name="direction"></param> /// <param name="currentClusterPin"></param> /// <param name="zoomLevel"></param> /// <param name="themeParameter"></param> private void addPinsWithinRange(List<ClusteredPin> pins, int index, int direction, ClusteredPin currentClusterPin, int zoomLevel, string themeParameter) { bool finished = false; int searchindex; searchindex = index + direction; while (!finished) { if (searchindex >= pins.Count || searchindex < 0) { finished = true; } else { if (!pins[searchindex].IsClustered) { // if (pins[searchindex].ThemeParameter == themeParameter)//only cluster if parameter are the same //{ if (Math.Abs(pins[searchindex].GetPixelX(zoomLevel) - pins[index].GetPixelX(zoomLevel)) < CLUSTERWIDTH) //within the same x range { if (Math.Abs(pins[searchindex].GetPixelY(zoomLevel) - pins[index].GetPixelY(zoomLevel)) < CLUSTERHEIGHT) //within the same y range = cluster needed { //add to cluster currentClusterPin.AddPin(pins[searchindex]); //stop any further clustering pins[searchindex].IsClustered = true; } } else { finished = true; } //} }; searchindex += direction; } } }
/// <summary> /// Adds pins to nearby cluster /// </summary> /// <param name="pins"></param> /// <param name="index"></param> /// <param name="direction"></param> /// <param name="currentClusterPin"></param> /// <param name="zoomLevel"></param> /// <param name="clusterWidth"></param> /// <param name="clusterHeight"></param> private void addPinsWithinRange(List<ClusteredPin> pins, int index, int direction, ClusteredPin currentClusterPin, int zoomLevel, int clusterWidth, int clusterHeight) { bool finished = false; int searchindex; searchindex = index + direction; while (!finished) { if (searchindex >= pins.Count || searchindex < 0) { finished = true; } else { if (!pins[searchindex].IsClustered) { if (Math.Abs(pins[searchindex].GetPixelX(zoomLevel) - pins[index].GetPixelX(zoomLevel)) < clusterWidth) //within the same x range { if (Math.Abs(pins[searchindex].GetPixelY(zoomLevel) - pins[index].GetPixelY(zoomLevel)) < clusterHeight) //within the same y range = cluster needed { //add to cluster currentClusterPin.AddPin(pins[searchindex]); //stop any further clustering pins[searchindex].IsClustered = true; } } else { finished = true; } }; searchindex += direction; } } }
/// <summary> /// Visual clustering. /// </summary> /// <param name="pins"></param> /// <param name="index"></param> /// <param name="direction"></param> /// <param name="currentClusterPin"></param> /// <param name="zoomLevel"></param> /// <param name="minClusterDistance"></param> private void addClusteredPinsWithinRange(List <ClusteredPin> pins, int index, int direction, ClusteredPin currentClusterPin, int zoomLevel, int minClusterDistance) { bool finished = false; int searchindex; searchindex = index + direction; while (!finished) { if (searchindex >= pins.Count || searchindex < 0) { finished = true; } else { if (!pins[searchindex].IsClustered) { if (Math.Abs(pins[searchindex].GetPixelX(zoomLevel) - pins[index].GetPixelX(zoomLevel)) < minClusterDistance) //within the same x range { if (Math.Abs(pins[searchindex].GetPixelY(zoomLevel) - pins[index].GetPixelY(zoomLevel)) < minClusterDistance) //within the same y range = cluster needed { //add to cluster currentClusterPin.CombineClusteredPins(pins[searchindex]); //stop any further clustering pins[searchindex].IsClustered = true; } } else { finished = true; } } ; searchindex += direction; } } }
/// <summary> /// Adds pins to nearby cluster for theme /// </summary> /// <param name="pins"></param> /// <param name="index"></param> /// <param name="direction"></param> /// <param name="currentClusterPin"></param> /// <param name="zoomLevel"></param> /// <param name="themeParameter"></param> private void addPinsWithinRange(List <ClusteredPin> pins, int index, int direction, ClusteredPin currentClusterPin, int zoomLevel, string themeParameter) { bool finished = false; int searchindex; searchindex = index + direction; while (!finished) { if (searchindex >= pins.Count || searchindex < 0) { finished = true; } else { if (!pins[searchindex].IsClustered) { // if (pins[searchindex].ThemeParameter == themeParameter)//only cluster if parameter are the same //{ if (Math.Abs(pins[searchindex].GetPixelX(zoomLevel) - pins[index].GetPixelX(zoomLevel)) < CLUSTERWIDTH) //within the same x range { if (Math.Abs(pins[searchindex].GetPixelY(zoomLevel) - pins[index].GetPixelY(zoomLevel)) < CLUSTERHEIGHT) //within the same y range = cluster needed { //add to cluster currentClusterPin.AddPin(pins[searchindex]); //stop any further clustering pins[searchindex].IsClustered = true; } } else { finished = true; } //} } ; searchindex += direction; } } }
public List<ClusteredPin> transformSeriesDataCartIntoClusteredPin(List<TimeSeriesViewModel> series, clientFilterAndSearchCriteria filterAndSearchCriteria) { List<ClusteredPin> clusterPins = new List<ClusteredPin>(); bool bUseFilter = ! (null == filterAndSearchCriteria || filterAndSearchCriteria.isEmpty()); if ( bUseFilter) { //Retrieve COPIES of all time series which match filter criteria... series = filterTimeSeries(series, filterAndSearchCriteria); } for (int i = 0; i < series.Count; i++) { var cl = new ClusteredPin(); cl.Loc = new LatLong(series[i].Latitude, series[i].Longitude); cl.assessmentids.Add(series[i].SeriesId); cl.PinType = "point"; //Retain the service code, title and highest value count for later reference... string key = series[i].ServCode; string title = series[i].ServTitle; cl.ServiceCodeToTitle[key] = title; clusterPins.Add(cl); } return clusterPins; }