Sunday, January 4, 2009
Map Point Label Placement
In this post, I will be demonstrating how to label map points using the flex API for AGS. This work is strongly based on Kevin Mote’s “Fast Point-Feature Label Placement For Dynamic Visualization”. In this application, I’m loading city data from a shapefile. The city attributes contain the city name and rank. Rank is a number between 1 and 7 where 1 indicates a very populated city and 7 indicates a much lesser populated city. Upon a map load event, the shapefile is read and the cities with a rank less or equal to 3 are selected then sorted in descending rank order. Each shapefile record is converted into a LabelGraphic and added to a list. A LabelGraphic is a subclass of Graphic and has a TextField as a child. A ExtentChange event listener is added to the map, in such a way that whenever the map extent changes, a LabelManager is created to manage the LabelGraphics in the visible extent. A LabelManager has a spatial index (a trellis per Mote’s) to quickly locate label candidates (more on this later) and has a list of Features in ascending priority. LabelGraphics should be added in incremental order of priority to the label manager. Each added LabelGraphic is wrapped with a Feature from which, four label candidates are derived. The label of a feature can reside in one of the four corners (candidate) around the feature; upper-right, lower-right, upper-left, lower-left. The later order is an “aesthetic” priority per Hartmann et al as discussed in Mote’s paper. Once all the LabelGraphics are added, the solve function is invoked. There, the “cost” of each feature is calculated in such a way that higher priority features are greatly weighted compare to lower priority features (Mote’s cost formula is applied here) In addition, in that step any feature candidate that is not fully visible on the map is removed. The features are in a stack and are popped one at the time, and for each feature candidate, we find all the overlapping candidates from the other features (the spatial index or the trellis comes very handy here :-) and we calculate the cost of that candidate based on the sum of the overlapping candidate. The candidate with the least cost sum is selected to label that feature and the TextField associated with the parent’s feature LabelGraphic is repositioned to “properly” label the feature upon the update of the display list. The other candidates in that feature are removed and the candidates from the other features that overlap the selected candidate are removed as feature label candidates. Now, something very important; the cost of the removed candidate is added to the feature in such a way that it makes is more costly to remove a candidate of a feature with fewer and fewer candidates. Due to this process of candidate elimination, lower priority features could not be labeled, but that is ok; that is why they are at a lower priority :-) There are differences between Mote’s “Trellis Strategy” and this implementation; In the Mote’s approach each feature is optimized for a uniformly sized labels where in this implementation each feature can be labeled with its own text format. In addition, the trellis is reconstructed on each extent change based on visible features, eliminating the cost adjustment based on distance. And finally, the trellis cell size is fixed and proportional to the map pixel width and height. You can see the application in action here, and like usual you can download the source code from here.