Powerlaw scaling Pushpin in the Virtual Earth Silverlight control

posted in: Uncategorized | 0

johnWeeGo[1] I was asked on the MSDN VE forum if the new Silverlight control had pushpins and indeed clustered pushpins. The answer is that it doesn’t have the concept of a pushpin but rather a much more flexible method of attaching any UIElement to the map.

pinstreet

In terms of clustering this is very possible but today I’d like to start by showing Powerlaw scaling from Lutz applied to the VE control.

Powerlaw scaling applies a scale transform to the object based on the zoomlevel. It makes the object full size at street level and tiny at world level. It produces a more realistic effect as you zoom in and out where a static sized object appears to grow as you zoom out.

pinworld

The interesting part of this is it allows you to show much more information on the map without pins overlapping. It will reduce the situations where you need to cluster your data.

The formula I use is:

Math.Pow(0.05*(currentZoomLevel + 1), 2) + 0.01

Adding a Pushpin

Since the control has no built in Pushpin lets make our own, it will simply be a Silverlight control called pin.xaml.

addcontrol

For the XAML we will have Grid with the scale transform and an Image control:

<UserControl x:Class="SoulSolutions.VESL.CustomPushPin.Pin"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
        <Grid.RenderTransform>
            <ScaleTransform x:Name="PinScaleTransform" ScaleX="1" ScaleY="1" />
        </Grid.RenderTransform>
        <Image Width="250" Stretch="Uniform" x:Name="PinImage"></Image>
    </Grid>
</UserControl>

Note we gave the scale transform and the image a name so we can access these in the code behind:

using System;
using System.Windows.Media;
using Microsoft.VirtualEarth.MapControl;

namespace SoulSolutions.VESL.CustomPushPin
{
    public partial class Pin
    {
        public Pin()
        {
            InitializeComponent();
        }

        private Map _map;
        public Map MapInstance {
            get
            {
                return _map;
            } 
            set
            {
                _map = value;
                _map.ViewChangeOnFrame += MapViewChangeOnFrame;
                ApplyPowerLawScaling(_map.ZoomLevel);
            }
        }

        public ImageSource ImageSource
        {
            get { return PinImage.Source; }
            set { PinImage.Source = value; }
        }

        void MapViewChangeOnFrame(object sender, MapEventArgs e)
        {
            ApplyPowerLawScaling(MapInstance.ZoomLevel);
        }

        private void ApplyPowerLawScaling(double currentZoomLevel)
        {
            double scale = Math.Pow(0.05*(currentZoomLevel + 1), 2) + 0.01;
            if (scale > 1) scale = 1;
            if (scale < 0.125) scale = 0.125;
            PinScaleTransform.ScaleX = scale;
            PinScaleTransform.ScaleY = scale;
        }
    }
}

Our class has two public properties, the MapInstance, set so we can access the map itself to get the zoomlevel and listen to the ViewChangeOnFrame event, and the ImageSource property to easily set the actual image for the pin.

When the MapInstance is set we apply the initial scaling, same on every frame of ViewChange. The ApplyPowerLawScaling simply applies the formula with some min/max thresholds.

To add the pin to the map we create a new layer and use the AddChild() methods like so:

var layer = new MapLayer();
map.AddChild(layer);

//Sydney
layer.AddChild(new Pin
{
    ImageSource = new BitmapImage(new Uri("pin.png", UriKind.Relative)),
    MapInstance = map
}, new Location(-33.86643, 151.2062), PositionMethod.Center);

Pretty cool hey? See it in action here. Download the full source here (162KB).

Next steps

If you’re interested I can show you how to implement IDisposable to clean up events and stop an animation you may want to be running on your pins as well as extend the control to have a label and a infobox balloon. Leave me a comment if you’re interested.