Home > database >  How to map image coordinates to Latitudes and Longitudes in WPF?
How to map image coordinates to Latitudes and Longitudes in WPF?

Time:12-23

I have a static world map of size 500*500px and I am taking latitudes and Longitudes as user input and trying to map the same location on the map using a dot.

I am using these formulas to convert the pixel values into coordinates and vice versa. Currently it is showing wrong location for any latitude and longitudes I input.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;

namespace MapControl.ProjectionFolder
{
    public class GoogleMapsAPIProjection : IProjection
    {
        private readonly double PixelTileSize = 256d;
        private readonly double DegreesToRadiansRatio = 180d / Math.PI;
        private readonly double RadiansToDegreesRatio = Math.PI / 180d;
        private readonly PointF PixelGlobeCenter;
        private readonly double XPixelsToDegreesRatio;
        private readonly double YPixelsToRadiansRatio;

        public GoogleMapsAPIProjection(double zoomLevel)
        {
            var pixelGlobeSize = this.PixelTileSize * Math.Pow(2d, zoomLevel);
            this.XPixelsToDegreesRatio = pixelGlobeSize / 360d;
            this.YPixelsToRadiansRatio = pixelGlobeSize / (2d * Math.PI);
            var halfPixelGlobeSize = Convert.ToSingle(pixelGlobeSize / 2d);
            this.PixelGlobeCenter = new PointF(halfPixelGlobeSize, halfPixelGlobeSize);
        }

        #region IProjection Members

        public PointF FromCoordinatesToPixel(PointF coordinates)
        {
            var x = Math.Round(this.PixelGlobeCenter.X   (coordinates.X * this.XPixelsToDegreesRatio));
            var f = Math.Min(Math.Max(Math.Sin(coordinates.Y * RadiansToDegreesRatio), -0.9999d), 0.9999d);
            var y = Math.Round(this.PixelGlobeCenter.Y   .5d * Math.Log((1d   f) / (1d - f)) * -this.YPixelsToRadiansRatio);
            return new PointF(Convert.ToSingle(x), Convert.ToSingle(y));
        }

        public PointF FromPixelToCoordinates(PointF pixel)
        {
            var longitude = (pixel.X - this.PixelGlobeCenter.X) / this.XPixelsToDegreesRatio;
            var latitude = (2 * Math.Atan(Math.Exp((pixel.Y - this.PixelGlobeCenter.Y) / -this.YPixelsToRadiansRatio)) - Math.PI / 2) * DegreesToRadiansRatio;
            return new PointF(Convert.ToSingle(latitude), Convert.ToSingle(longitude));
        }

        #endregion
    }
}

This is my XAML Code.

<Window x:Class="MapControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MapControl"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <ScrollViewer HorizontalScrollBarVisibility="Auto" Grid.Row="4" Width="500" Height="500" Background="Gray">
        <Grid Width="500" Height="500">
            <Image Margin="0,0,0,0" x:Name="MapImage" Source="Desktop\screenmap.png" Stretch="Fill" Width="500" Height="500" ></Image>
            <Ellipse Canvas.Top="50" 
      Canvas.Left="50" 
      Fill="Red" 
      Height="5"
      Width="5"
      Visibility="Collapsed"
      StrokeThickness="4"                     

      x:Name="CircleEllipse"
      HorizontalAlignment="Left"
      VerticalAlignment="Top"
      Margin="0,0,0,0" />
        </Grid>
    </ScrollViewer>
</Window>

And this is how I am calling the functions.

using MapControl.ProjectionFolder;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MapControl
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            GoogleMapsAPIProjection googleApiProjObj = new GoogleMapsAPIProjection(0);
            float x = 18.29F;
            float y = 73.57F;
            System.Drawing.PointF p1 = new System.Drawing.PointF(x, y);
            System.Drawing.PointF p2 = googleApiProjObj.FromCoordinatesToPixel(p1);

        //CircleEllipse.Margin = new Thickness(longitudePixels,latitudePixels, 0, 0);
        CircleEllipse.Margin = new Thickness(p2.X, p2.Y, 0, 0);
        CircleEllipse.Visibility = Visibility.Visible;
        }
        
    }
}


CodePudding user response:

Converting from view coordinates to geodetic coordinates (and back) is a two step process.

First you have to convert from view coordinates (e.g. a point relative to the upper left corner of an Image element) to projected map coordinates, and then from projected coordinates to geodetic location. For the conversion back from location to view coordinates, you would first convert from geodetic location to projected map coordinates and then from map coordinates to view coordinates.

The conversion from geodetic to projected map coordinates involves a scale factor that is typically something like degree-to-meters, but can also be any other arbitrary value. In the example below it is 1.0 for simplicity.

You also need to specify the bounds of the visible part of the map, i.e. its south, west, north and east boundaries.

The example class below implements the web mercator projection. It needs a single setup by a SetViewport method where you specify the map bounds and view size.

You can then pass view coordinates to the ViewToLocation to get the geodetic coordinates of a view point, and pass a location to LocationToView for the opposite conversion.

Note that for geodetic locations Point.X holds the longitude and Point.Y the latitude, and that projected map coordinates go from bottom/left to top/right, like longitude and latitude, while view coordinates go from top/right to bottom/left as usual.

public class WebMercatorView
{
    private Rect mapBounds;
    private Size viewSize;

    /// <summary>
    /// Sets visible map boundaries and view size.
    /// </summary>
    public void SetViewport(Point southWest, Point northEast, Size viewSize)
    {
        mapBounds = new Rect(LocationToMap(southWest), LocationToMap(northEast));
        this.viewSize = viewSize;
    }

    /// <summary>
    /// Converts from geodetic location to view coordinates.
    /// </summary>
    public Point LocationToView(Point location)
    {
        Point mapPoint = LocationToMap(location);

        return new Point(
            viewSize.Width * (mapPoint.X - mapBounds.X) / mapBounds.Width,
            viewSize.Height * (1d - (mapPoint.Y - mapBounds.Y) / mapBounds.Height));
    }

    /// <summary>
    /// Converts from view coordinates to geodetic location.
    /// </summary>
    public Point ViewToLocation(Point point)
    {
        Point mapPoint = new Point(
            point.X / viewSize.Width * mapBounds.Width   mapBounds.X,
            (1d - point.Y / viewSize.Height) * mapBounds.Height   mapBounds.Y);

        return MapToLocation(mapPoint);
    }

    /// <summary>
    /// Converts geo coordinates (lon/lat in degrees)
    /// to map coordinates (x/y in range -180..180 for a square map)
    /// </summary>
    private static Point LocationToMap(Point location)
    {
        Point point = location;

        if (location.Y <= -90d)
        {
            point.Y = double.NegativeInfinity;
        }
        else if (location.Y >= 90d)
        {
            point.Y = double.PositiveInfinity;
        }
        else
        {
            point.Y = Math.Log(Math.Tan((location.Y   90d) * Math.PI / 360d))
                    * 180d / Math.PI;
        }

        return point;
    }

    /// <summary>
    /// Converts map coordinates (x/y in range -180..180 for a square map)
    /// to geo coordinates (lon/lat in degrees)
    /// </summary>
    private static Point MapToLocation(Point point)
    {
        return new Point(
            point.X,
            90d - Math.Atan(Math.Exp(-point.Y * Math.PI / 180d)) * 360d / Math.PI);
    }
}

Assuming you have an Image element that shows a map image with bounds East=8.08594, South=53.54031, West=8.43750 and North=53.74871 and SizeChanged and MouseMove event handlers, you could use the class like this:

private readonly WebMercatorView webMercatorView = new WebMercatorView();

private void ImageSizeChanged(object sender, SizeChangedEventArgs e)
{
    var image = (Image)sender;

    webMercatorView.SetViewport(
        new Point(8.08594, 53.54031),
        new Point(8.43750, 53.74871),
        image.RenderSize);
}

private void ImageMouseMove(object sender, MouseEventArgs e)
{
    var image = (Image)sender;

    var location = webMercatorView.ViewToLocation(e.GetPosition(image));

    Debug.WriteLine($"location: {location.Y:F6},  {location.X:F6}");

    var point = webMercatorView.LocationToView(location);

    Debug.WriteLine($"point: {point.X:F0},  {point.Y:F0}");
}
  • Related