This project has moved. For the latest updates, please go here.

Question: How to display an image on the map based on transformed coordinates?

May 11, 2013 at 11:19 AM
Hi,
I would like to load an image, place it on the center of the map and then bond it with the map so it pans and zooms together with the map. I figured out how to determine the coordinates of the image and create the objects but the Image is not visible and the map crashes with an unhandled exception. It would be great if someone can point out my mistakes.

I removed the MapImage part in the demo WinRT application and this is the code of my code behind file:
using System;
using System.Collections.Generic;
using MapControl;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace StoreApplication
{
    using Windows.Foundation;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Media.Imaging;

    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private SamplePoint movingPoint = new SamplePoint
        {
            Name = "Moving",
            Location = new Location(53.5, 8.25)
        };

        public MainPage()
        {
            this.InitializeComponent();

            var polylines = (ICollection<object>)Resources["Polylines"];
            polylines.Add(
                new SamplePolyline
                {
                    Locations = LocationCollection.Parse("53.5140,8.1451 53.5123,8.1506 53.5156,8.1623 53.5276,8.1757 53.5491,8.1852 53.5495,8.1877 53.5426,8.1993 53.5184,8.2219 53.5182,8.2386 53.5195,8.2387")
                });
            polylines.Add(
                new SamplePolyline
                {
                    Locations = LocationCollection.Parse("53.5978,8.1212 53.6018,8.1494 53.5859,8.1554 53.5852,8.1531 53.5841,8.1539 53.5802,8.1392 53.5826,8.1309 53.5867,8.1317 53.5978,8.1212")
                });

            var points = (ICollection<object>)Resources["Points"];
            points.Add(
                new SamplePoint
                {
                    Name = "Steinbake Leitdamm",
                    Location = new Location(53.51217, 8.16603)
                });
            points.Add(
                new SamplePoint
                {
                    Name = "Buhne 2",
                    Location = new Location(53.50926, 8.15815)
                });
            points.Add(
                new SamplePoint
                {
                    Name = "Buhne 4",
                    Location = new Location(53.50468, 8.15343)
                });
            points.Add(
                new SamplePoint
                {
                    Name = "Buhne 6",
                    Location = new Location(53.50092, 8.15267)
                });
            points.Add(
                new SamplePoint
                {
                    Name = "Buhne 8",
                    Location = new Location(53.49871, 8.15321)
                });
            points.Add(
                new SamplePoint
                {
                    Name = "Buhne 10",
                    Location = new Location(53.49350, 8.15563)
                });
            points.Add(movingPoint);

            var pushpins = (ICollection<object>)Resources["Pushpins"];
            pushpins.Add(
                new SamplePoint
                {
                    Name = "WHV - Eckwarderhörne",
                    Location = new Location(53.5495, 8.1877)
                });
            pushpins.Add(
                new SamplePoint
                {
                    Name = "JadeWeserPort",
                    Location = new Location(53.5914, 8.14)
                });
            pushpins.Add(
                new SamplePoint
                {
                    Name = "Kurhaus Dangast",
                    Location = new Location(53.447, 8.1114)
                });
            pushpins.Add(
                new SamplePoint
                {
                    Name = "Eckwarderhörne",
                    Location = new Location(53.5207, 8.2323)
                });

            var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(0.1) };
            timer.Tick += MovePoint;
            timer.Start();

            Loaded += MainPage_Loaded;
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            SetUpMapImage();
        }

        private MapImage mapImage;
        private void SetUpMapImage()
        {


            //lets pan to a fixed position
            Location location = new Location(53.54031, 8.08594);
            map.Center = location;

            //this is the original XAML code we first try to reproduce via C#
            /*<!--<map:MapImage x:Name="mapImage" South="53.54031" North="53.74871" West="8.08594" East="8.43750"
                          Source="10_535_330.jpg" Opacity="0.5"/>-->*/

            mapImage = new MapImage();
            /*BitmapSource bitmapSource = new BitmapImage(new Uri("ms-appx:///10_535_330.jpg"));
            
            mapImage.Source = bitmapSource;
            mapImage.West = 8.08594;
            mapImage.South = 53.54031;
            mapImage.North = 53.74871;
            mapImage.East = 8.43750;*/
            
            //note: it worked but when we set MapPanel.SetLocation(mapImage,location), the image is no longer visible.
            //MapPanel.SetLocation(mapImage,location);


            //now we would like to load an image, place it on top of the map and then bond it with the map so we can zoom and pan around
            BitmapSource bitmapSource = new BitmapImage(new Uri("ms-appx:///10_535_330.jpg")); //width = 256, height=256
            mapImage.Source = bitmapSource;
            
            
            //step 1: we assume centerOfMap = centerOfMapScreen, thereby having a common point in both coordiate systems
            Location centerOfMap = map.Center;
            Point centerOfMapScreen = new Point(map.ActualWidth/2,map.ActualHeight/2);

            //and let us place the image center on the center of the map 
            Point centerOfImage = centerOfMapScreen;

            //get the 4 points of image, TopLeft, TopRight, BottomLeft, BottomRight
            var TLPoint = new Point(
               centerOfImage.X - 256 / 2, centerOfImage.Y - 256 / 2);
            var TRPoint = new Point(
                centerOfImage.X + 256 / 2, centerOfImage.Y - 256 / 2);
            var BLPoint = new Point(
                centerOfImage.X - 256 / 2, centerOfImage.Y + 256 / 2);
            var BRPoint = new Point(
                centerOfImage.X + 256 / 2, centerOfImage.Y + 256 / 2);
            
            //get Location coordinates of each point
            var TLLocation = this.map.MapTransform.Transform(TLPoint);
            var TRLocation = this.map.MapTransform.Transform(TRPoint);
            var BLLocation = this.map.MapTransform.Transform(BLPoint);
            var BRLocation = this.map.MapTransform.Transform(BRPoint);
            
            //determine bounding box, note south=TL and north=BL, as MapRectangle asks for North to be bigger than south
            var westLongitude = TLLocation.Longitude < BLLocation.Longitude ? TLLocation.Longitude : BLLocation.Longitude;
            var eastLongitude = TRLocation.Longitude > BRLocation.Longitude ? TRLocation.Longitude : BRLocation.Longitude;
            var southLatitude = TLLocation.Latitude > TRLocation.Latitude ? TLLocation.Latitude : TRLocation.Latitude;
            var northLatitude = BLLocation.Latitude < BRLocation.Latitude ? BLLocation.Latitude : BRLocation.Latitude;

            //set coordinates
            mapImage.West = westLongitude;
            mapImage.East = eastLongitude;
            mapImage.North = northLatitude;
            mapImage.South = southLatitude;

            map.Children.Add(mapImage);

            //Expected Result: Image on map
            //Actual Result: No image visible and map crashes after a couple of zooms
        }

        private void MovePoint(object sender, object e)
        {
            movingPoint.Location = new Location(movingPoint.Location.Latitude + 0.001, movingPoint.Location.Longitude + 0.002);

            if (movingPoint.Location.Latitude > 54d)
            {
                movingPoint.Name = "Stopped";
                ((DispatcherTimer)sender).Stop();
            }
        }

        private void ImageOpacitySliderValueChanged(object sender, RangeBaseValueChangedEventArgs e)
        {
            if (mapImage != null)
            {
                mapImage.Opacity = e.NewValue / 100;
            }
        }

        private void SeamarksClick(object sender, RoutedEventArgs e)
        {
            var checkBox = (CheckBox)sender;
            var tileLayers = (TileLayerCollection)Resources["TileLayers"];
            var tileLayer = tileLayers["Seamarks"];

            if ((bool)checkBox.IsChecked)
            {
                map.TileLayers.Add(tileLayer);
            }
            else
            {
                map.TileLayers.Remove(tileLayer);
            }
        }

        private void TileLayerSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (map != null)
            {
                var comboBox = (ComboBox)sender;
                var tileLayers = (TileLayerCollection)Resources["TileLayers"];
                map.TileLayer = tileLayers[(string)((ComboBoxItem)comboBox.SelectedItem).Content];
            }
        }
    }
}
Coordinator
May 11, 2013 at 2:23 PM
First, it would have been easier for me to see what you did when you had posted only the relevant parts of the code, not just the whole source file.

Anyway, you used the wrong transformation. MapTransform transforms from Lat/Lon to logical map coordinates, not viewport coordinates. You would have to use the MapBase.ViewportPointToLocation method instead. Also, there is no need to transform four points. Two of them, (e.g. Top/Left and Bottom/Right) are enough.
var bitmap = new BitmapImage(new Uri("ms-appx:/10_535_330.jpg"));
var bitmapWidth = 256;
var bitmapHeight = 256;
var topLeft = new Point((map.ActualWidth - bitmapWidth) / 2,
                        (map.ActualHeight - bitmapHeight) / 2);
var bottomRight = new Point((map.ActualWidth + bitmapWidth) / 2,
                            (map.ActualHeight + bitmapHeight) / 2);
var tlLocation = map.ViewportPointToLocation(topLeft);
var brLocation = map.ViewportPointToLocation(bottomRight);

var mapImage = new MapImage
{
    West = tlLocation.Longitude,
    East = brLocation.Longitude,
    North = tlLocation.Latitude,
    South = brLocation.Latitude,
    Source = bitmap
};

map.Children.Add(mapImage);
Another note: setting MapPanel.Location on a MapImage doesn't make sense, as it does not have such a location. MapPanel.Location is meant for point-like objects like Pushpins that are placed at exactly only location and only transform their position, but not their extent and rotation with the map. MapImage (or MapRectange and MapPolyline) are different. They define an area that is transformed with the map. That's why MapRectangle has the West, East, North and South properties.
May 11, 2013 at 4:37 PM
Hi,
thank you so much for your help.
I'm working with four points, because I implement a geocoding algorithm to allow for non affine four point transformations of images on the map. Thanks to your help, it's working :-)

It would be helpful to have SetLocation throw some kind of exception. Without looking into the code I assumed it set some kind of center and the west, north, .. coordinates espanded the content. Anyway..

There is one last issue I have: I set MaxZoomLevel to 25, yet when zooming in, about half way the MapImage gets hidden. Is there anything I can do about it?

Thanks so much.

Andreas
Coordinator
May 11, 2013 at 5:07 PM
Edited May 11, 2013 at 10:34 PM
Perhaps the image gets hidden because of overflows in the transformed viewport coordinates, as it may get really large when zoomed in that far. However, it's a framework problem. I'd suggest to somehow limit the zoom range in a way that the transformed image won't get arbitrarily large.
May 11, 2013 at 10:10 PM
It's possible but I guess it's somewhere else.
Here is a screenshot. The Image should be located between the four Pushpins. (The area on the right has been removed in the screenshot) The screenshot is 1:1 screen size
Image

Here is a screenshot that shows the amount I need to zoom out before the Image appears. If I zoom back in just slightly, the image gets hidden again.
At the bottom of the screenshot you see the original Image (taken from Wikipedia/Wikicommons) in original size.
Image

I don't modify the size of the image up to that point and just use the algo you provided above. The image gets inserted at map zoom level 16.

Is there any possibility to debug this?
Coordinator
May 11, 2013 at 10:45 PM
No idea what's going on there. I can see the same effect in the sample StoreApplication. The image disappears when it reaches a certain size. When I continue to zoom in it re-appears at some point, before it finally disappears again. This doesn't happen in the WPF or Silverlight versions, only in WinRT. Maybe a bug in the framework. Don't know how to debug this.
May 17, 2013 at 4:10 AM
I did some further tests.
I placed a MapRectangle behind the Image of the store application.
1) The image does not just disappear, it has some strange changes at the border. See image below.
2) It happens with the MapRectangle too, however at a larger zoom Level.

Can you point me as to where in MapControl the rectangles and Images are actually transformed in size when zooming? I haven't really found out yet.

Image
Coordinator
May 17, 2013 at 6:14 AM
The size transform is done by assigning the parent map's ViewportTransform to the RenderTransform of a MapRectangle. The assignment is in MapRectangle.cs, line 79, in a method called UpdateGeometry, which is called when the West, East, South or North properties have changed.

MapImage has another transform that scales by a factor of -1 in y direction, which is set once to its Fill property when the Source property has changed.