If you have ever tried to show more than a hundred pushpins in a non-static map control, you will find that memory starts blowing up, and the map interaction begins to slow to a crawl. The common solution to this has always been clustering, i.e., grouping your pushpins together into aggregate groups and showing fewer pushpins. While this may work for most implementations, it fails to address the case where, “in order for the user to make any sense of the data, you simply must show hundreds of pushpins at the same time.” Consider the following case from the upcoming FNC Terminal View for Origination Spring 2014 demo. (Note this scenario is in fact part of the Winter 2014 demo – however, in its non-quad tree form.)
In this example, we are showing around 9,000 properties, grouped by X criteria, color-coded by “days in the system.” Our functional requirement was to allow real-time drill-down into this data set. Early versions of this demo actually used pushpins with clustering, and over the next several iterations, one thing became evident.
“When it comes to showing large data sets in real-time map controls, the single largest bottleneck in the whole system is rendering/processing the ‘interactive,’ i.e., clickable, part of the control (rendered map).”
Having come from a graphics programming background (I cut my teeth on Mode 13h) I was sure I could draw them faster with a more “specialized” approach.
The fly in the ointment
Given our specific requirements, in each “frame” we do the following:
Determine if the location is within the current “view port”
If the location is in view, determine the correct color (by age)
Draw the location on the map using “age” color
Because we allow the user to toggle visibility by “age,” we need to add a step:
Determine if the location is within the current “view port”
If the location is in view and If age group is selected
Determine the correct color (by age)
Draw the location on the map using “age” color
Initially this code looks something like (foreach used for simplicity):
public class DynamicParameters { public Color AgeColor { get; set; } public string Field { get; set; } public string Value { get; set; } } public class MapItem { public Location Location { get; set; } public Dictionary<string, string> Attributes { get; set; } public Point ScreenXY { get; set; } } public MapCore map; ... foreach (DynamicParameters dp in DynamicParametersList) foreach (MapItem ll in LocationList) { //translate lat/lon to screen space Point ScreenXY = map.LocationToViewportPoint(ll.Location); //if the point falls within the bounds of our viewport if(ScreenXY.X >= 0 && ScreenXY.X < map.ActualWidth && ScreenXY.Y >= 0 && ScreenXY.Y < map.ActualHeight) { //if we pass the age check if(ll.Attributes[dp.Field].Equals(dp.Value)) { //draw the icon,xy,color DrawIcon(ll,ScreenXY,dp.AgeColor); } } }
Aside from having to convert every location into screen space to test for visibility, no matter what we come up with, we are ultimately forced to visit every array element, every frame.
Stay tuned for next week’s post, where I will introduce the quad tree and show how to use it to speed up our real-time rendering.
Pingback: Adventures in mapping BIG DATA SETS in real-time map controls, part 2: Enter the quadtree |
Pingback: Adventures in mapping BIG DATA SETS in real-time map controls, Part 3: Using quadtrees in the pipeline map plugin |