A Constant Size Bing Maps PushPin With Silverlight

Chris Pietschmann has written an excellent article on how to resize and auto-scale pushpins using the Bing Maps Silverlight control. I found it very useful but quickly stumbled on to the fact that his simple scaling algorithm:

var scaleVal = (0.05 * (currentZoomLevel + 1)) + 0.3;

did not show much difference in size across the ZoomLevels and didn’t do what I desired.

In the default Bing Map, a PushPin always has the same size (35 pixels) regardless of the ZoomLevel, what I was looking for was a PushPin that always had the same size in meters regardless of the ZoomLevel. In other words, a PushPin that was sized to 100 meters would be really big at ZoomLevel 18, and too small to be seen at ZoomLevel 1.

Using the published scaling factors for Bing Maps (conveniently powers of 2) it was pretty straight forward to write a IValueConverter. Internally there are actually two methods, one assumes that the PushPin is at the equator, and the second allows you to specify a Latitude that will further scale the PushPin to compensate for the projection. This would be useful in an IMultiValueConverter.

This could probably also have been done with custom PushPins on a MapLayer.

I hope you enjoy this.

    -David

 

//
// Copyright 2010 David Robinson  All Rights Reserved
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that any
// reedistributions of source code must retain the above
// copyright notice and this condition.
//

using System;
using System.Windows.Data;
using System.Windows.Media;

namespace DaRobins
{
    /// <summary>
    /// Convert a ZoomLevel into to ScaleTransform
    /// </summary>
    public class PushPinScaleConverter : IValueConverter
    {
        #region IValueConverter Members

        /// <summary>
        /// Convert a ZoomLevel into to Scale
        /// </summary>
        //
        // The published Bing Maps Zoom scaling factors, in meters/pixel
        //     http://msdn.microsoft.com/en-us/library/aa940990.aspx
        //
        // Bing maps resolution in meters/pixel is defined are by the equation:
        //     (78271.52 / (2^(ZoomLevel-1))) * Cos(Latitude)
        //

        static private double[] BingMapsScaleFactor = { 78271.52, 78271.52, 39135.76, 19567.88,
                                                         9783.94,  4891.97,  2445.98,  1222.99,
                                                          611.50,   305.75,   152.87,    76.44,
                                                           38.22,    19.11,     9.55,     4.78,
                                                            2.39,     1.19,     0.60,     0.30 };

        /// <summary>
        /// Convert a Bing Maps ZoomLevel into meters per pixel, assumes at the equator.
        /// </summary>
        /// <param name="zoomLevel">The current ZoomLevel</param>
        /// <returns>The scale in meters per pixel</returns>
        static public double BingMapsScaleMetersPerPixel(double zoomLevel)
        {
            if (zoomLevel < 0 || zoomLevel > (BingMapsScaleFactor.Length - 1))
                return BingMapsScaleFactor[0];

            return BingMapsScaleFactor[(int)Math.Round(zoomLevel)];
        }

        /// <summary>
        /// Convert a Bing Maps ZoomLevel into meters per pixel, scaled with latitude
        /// </summary>
        /// <param name="zoomLevel">The current ZoomLevel</param>
        /// <param name="latitude">The current Latitude</param>
        /// <returns>The scale in meters per pixel</returns>
        static public double BingMapsScaleMetersPerPixel(double zoomLevel, double latitude)
        {
            if (zoomLevel < 0 || zoomLevel > BingMapsScaleFactor.Length)
                return BingMapsScaleFactor[0];

            return BingMapsScaleFactor[(int)Math.Round(zoomLevel)] * Math.Cos(Math.PI * latitude / 180);
        }

        /// <summary>
        /// Convert a ZoomLevel into to ScaleTransform
        /// </summary>
        /// <param name="value">The current ZoomLevel</param>
        /// <param name="targetType">The type of the target (unused)</param>
        /// <param name="parameter">The parameter (unused)</param>
        /// <param name="culture">The current culture (unused)</param>
        /// <returns>A ScaleTransform</returns>
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            double currentZoomLevel = (double)value;

            //
            // A PushPin is normally 35x35 pixels, at zoom level 15 we wouldlike that to be 1:1
            //

            var ScaleVal = BingMapsScaleFactor[15] / BingMapsScaleMetersPerPixel(currentZoomLevel);

            var transform = new ScaleTransform();
            transform.ScaleX = ScaleVal;
            transform.ScaleY = ScaleVal;

            //
            // Set the transform center X and Y so the Pushpin stays at the correct location.
            // The Default Pushpin's height is 35 and width is 34
            // Since the Default Pushpin's PositionOrigin is set to BottomCenter, we need to
            // set the CenterX to half the width (17), and CenterY to the height (35).
            //

            transform.CenterX = 17;
            transform.CenterY = 35;

            return transform;
        }

        /// <summary>
        /// Convert a ScaleTransform into to ZoomeLevel
        /// </summary>
        /// <param name="value">The current ScaleTransform</param>
        /// <param name="targetType">The type of the target (unused)</param>
        /// <param name="parameter">The parameter (unused)</param>
        /// <param name="culture">The current culture (unused)</param>
        /// <returns>A ZoomeLevel</returns>
        /// <exception cref="NotImplementedException">Always thrown</exception>
        /// <remarks>Unimplemented</remarks>
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s