Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change the coordinate system of a Canvas in WPF

I'm writing a mapping app that uses a Canvas for positioning elements. For each element I have to programatically convert element's Lat/Long to the canvas' coordinate, then set the Canvas.Top and Canvas.Left properties.

If I had a 360x180 Canvas, can I convert the coordinates on the canvas to go from -180 to 180 rather than 0 to 360 on the X axis and 90 to -90 rather than 0 to 180 on the Y axis?

Scaling requirements:

  • The canvas can be any size, so should still work if it's 360x180 or 5000x100.
  • The Lat/Long area may not always be (-90,-180)x(90,180), it could be anything (ie (5,-175)x(89,-174)).
  • Elements such as PathGeometry which are point base, rather than Canvas.Top/Left based need to work.
like image 780
Dylan Avatar asked Oct 31 '08 16:10

Dylan


2 Answers

Here's an all-XAML solution. Well, mostly XAML, because you have to have the IValueConverter in code. So: Create a new WPF project and add a class to it. The class is MultiplyConverter:

namespace YourProject
{
    public class MultiplyConverter : System.Windows.Data.IValueConverter
    {
        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return AsDouble(value)* AsDouble(parameter);
        }
        double AsDouble(object value)
        {
            var valueText = value as string;
            if (valueText != null)
                return double.Parse(valueText);
            else
                return (double)value;
        }

        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new System.NotSupportedException();
        }
    }
}

Then use this XAML for your Window. Now you should see the results right in your XAML preview window.

EDIT: You can fix the Background problem by putting your Canvas inside another Canvas. Kind of weird, but it works. In addition, I've added a ScaleTransform which flips the Y-axis so that positive Y is up and negative is down. Note carefully which Names go where:

<Canvas Name="canvas" Background="Moccasin">
    <Canvas Name="innerCanvas">
        <Canvas.RenderTransform>
            <TransformGroup>
                <TranslateTransform x:Name="translate">
                    <TranslateTransform.X>
                        <Binding ElementName="canvas" Path="ActualWidth"
                                Converter="{StaticResource multiplyConverter}" ConverterParameter="0.5" />
                    </TranslateTransform.X>
                    <TranslateTransform.Y>
                        <Binding ElementName="canvas" Path="ActualHeight"
                                Converter="{StaticResource multiplyConverter}" ConverterParameter="0.5" />
                    </TranslateTransform.Y>
                </TranslateTransform>
                <ScaleTransform ScaleX="1" ScaleY="-1" CenterX="{Binding ElementName=translate,Path=X}"
                        CenterY="{Binding ElementName=translate,Path=Y}" />
            </TransformGroup>
        </Canvas.RenderTransform>
        <Rectangle Canvas.Top="-50" Canvas.Left="-50" Height="100" Width="200" Fill="Blue" />
        <Rectangle Canvas.Top="0" Canvas.Left="0" Height="200" Width="100" Fill="Green" />
        <Rectangle Canvas.Top="-25" Canvas.Left="-25" Height="50" Width="50" Fill="HotPink" />
    </Canvas>
</Canvas>

As for your new requirements that you need varying ranges, a more complex ValueConverter would probably do the trick.

like image 121
Ryan Lundy Avatar answered Sep 28 '22 16:09

Ryan Lundy


I was able to get it to by creating my own custom canvas and overriding the ArrangeOverride function like so:

    public class CustomCanvas : Canvas
    {
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            foreach (UIElement child in InternalChildren)
            {
                double left = Canvas.GetLeft(child);
                double top = Canvas.GetTop(child);
                Point canvasPoint = ToCanvas(top, left);
                child.Arrange(new Rect(canvasPoint, child.DesiredSize));
            }
            return arrangeSize;
        }
        Point ToCanvas(double lat, double lon)
        {
            double x = this.Width / 360;
            x *= (lon - -180);
            double y = this.Height / 180;
            y *= -(lat + -90);
            return new Point(x, y);
        }
    }

Which works for my described problem, but it probably would not work for another need I have, which is a PathGeometry. It wouldn't work because the points are not defined as Top and Left, but as actual points.

like image 31
Dylan Avatar answered Sep 28 '22 16:09

Dylan