更改画布的原点

时间:2010-08-20 10:48:22

标签: wpf canvas coordinate-systems

我必须创建一个画布,其中包含与纬度和经度绑定位置的对象。

我的画布支持元素的拖动和缩放,但直到现在我总是使用标准坐标(从0,0开始)

现在我有GPS坐标并且所有物体彼此重叠,因为GPS坐标例如介于45°11'00'N和45°11'60N之间...所以如果我无法解决这个问题,基本上是1px的距离转换...画布也从0,0开始,我总是有45像素的白色空间

我可以检索最小的左侧和顶部值并重新调整大小但是如何将画布中的所有对象居中?

2 个答案:

答案 0 :(得分:0)

在某些时候会发表关于此事的博客文章,可能仍然会这样做。但是 - 简而言之 - 我只是通过编写自己的小组来解决这个问题,该小组了解如何根据墨卡托投影来定位孩子。

使用前需要了解的一些事项:

  • 您需要将MaxLatitudeMaxLongitude设置为背景图片支持的范围。
  • 我认为它还不支持双向更新。我打算在我的博客文章之前解决这个问题。

你这样使用它:

<ListBox ItemsSource="{Binding YourItems}">
    <ListBox.Template>
        <ControlTemplate>
            <Border BorderBrush="Black" BorderThickness="1" Background="#CEE3FF">
                <Grid>
                    <Grid.Resources>
                        <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}">Transparent</SolidColorBrush>
                        <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}">Transparent</SolidColorBrush>
                    </Grid.Resources>
                    <Image x:Name="mapImage" Source="YourMap.png"/>
                    <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" Width="{Binding ActualWidth, ElementName=mapImage}" Height="{Binding ActualHeight, ElementName=mapImage}" />
                </Grid>
            </Border>
        </ControlTemplate>
    </ListBox.Template>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <!-- make sure you set these values in line with YourMap.png -->
            <controls:MercatorProjectionPanel MaxLatitude="81" MinLatitude="-74"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="controls:MercatorProjectionPanel.Longitude" Value="{Binding Location.Longitude}"/>
            <Setter Property="controls:MercatorProjectionPanel.Latitude" Value="{Binding Location.Latitude}"/>
        </Style>
    </ListBox.ItemContainerStyle>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock>Here's your item</TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

以下是代码:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

/// <summary>
/// Implements a panel that lays out children according to the mercator projection.
/// </summary>
public class MercatorProjectionPanel : Panel
{
    /// <summary>
    /// Identifies the <see cref="MinLatitude"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty MinLatitudeProperty = DependencyProperty.Register(
        "MinLatitude",
        typeof(double),
        typeof(MercatorProjectionPanel),
        new FrameworkPropertyMetadata(DefaultMinLatitude, OnLatitudeRangeChanged));

    /// <summary>
    /// Identifies the <see cref="MaxLatitude"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty MaxLatitudeProperty = DependencyProperty.Register(
        "MaxLatitude",
        typeof(double),
        typeof(MercatorProjectionPanel),
        new FrameworkPropertyMetadata(DefaultMaxLatitude, OnLatitudeRangeChanged));

    /// <summary>
    /// Identifies the <c>Longitude</c> attached dependency property.
    /// </summary>
    public static readonly DependencyProperty LongitudeProperty = DependencyProperty.RegisterAttached(
        "Longitude",
        typeof(double),
        typeof(MercatorProjectionPanel),
        new FrameworkPropertyMetadata(double.NaN, OnGeographicalCoordinateChanged));

    /// <summary>
    /// Identifies the <c>Latitude</c> attached dependency property.
    /// </summary>
    public static readonly DependencyProperty LatitudeProperty = DependencyProperty.RegisterAttached(
        "Latitude",
        typeof(double),
        typeof(MercatorProjectionPanel),
        new FrameworkPropertyMetadata(double.NaN, OnGeographicalCoordinateChanged));

    /// <summary>
    /// Identifies the <c>Left</c> attached dependency property.
    /// </summary>
    public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached(
        "Left",
        typeof(double),
        typeof(MercatorProjectionPanel),
        new FrameworkPropertyMetadata(double.NaN, OnCoordinateChanged));

    /// <summary>
    /// Identifies the <c>Top</c> attached dependency property.
    /// </summary>
    public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached(
        "Top",
        typeof(double),
        typeof(MercatorProjectionPanel),
        new FrameworkPropertyMetadata(double.NaN, OnCoordinateChanged));

    private static readonly DependencyProperty XRatioProperty = DependencyProperty.RegisterAttached(
        "XRatio",
        typeof(double),
        typeof(MercatorProjectionPanel),
        new FrameworkPropertyMetadata(double.NaN));

    private static readonly DependencyProperty YRatioProperty = DependencyProperty.RegisterAttached(
        "YRatio",
        typeof(double),
        typeof(MercatorProjectionPanel),
        new FrameworkPropertyMetadata(double.NaN));

    private const double DefaultMinLatitude = -80;
    private const double DefaultMaxLatitude = 80;
    private const double DegreesPerRadian = 57.2957;

    private double minY = CalculateYRelative(DefaultMinLatitude);
    private double maxY = CalculateYRelative(DefaultMaxLatitude);

    /// <summary>
    /// Initializes a new instance of the MercatorProjectionPanel class.
    /// </summary>
    public MercatorProjectionPanel()
    {
        SizeChanged += delegate
        {
            InvalidateArrange();
        };
    }

    /// <summary>
    /// Gets or sets the minimum latitude displayed by this mercator projection panel.
    /// </summary>
    public double MinLatitude
    {
        get { return (double)GetValue(MinLatitudeProperty); }
        set { SetValue(MinLatitudeProperty, value); }
    }

    /// <summary>
    /// Gets or sets the maximum latitude displayed by this mercator projection panel.
    /// </summary>
    public double MaxLatitude
    {
        get { return (double)GetValue(MaxLatitudeProperty); }
        set { SetValue(MaxLatitudeProperty, value); }
    }

    /// <summary>
    /// Gets the longitude for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <returns>
    /// The longitude.
    /// </returns>
    public static double GetLongitude(DependencyObject dependencyObject)
    {
        return (double)dependencyObject.GetValue(LongitudeProperty);
    }

    /// <summary>
    /// Sets the longitude for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <param name="longitude">
    /// The longitude.
    /// </param>
    public static void SetLongitude(DependencyObject dependencyObject, double longitude)
    {
        dependencyObject.SetValue(LongitudeProperty, longitude);
    }

    /// <summary>
    /// Gets the latitude for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <returns>
    /// The latitude.
    /// </returns>
    public static double GetLatitude(DependencyObject dependencyObject)
    {
        return (double)dependencyObject.GetValue(LatitudeProperty);
    }

    /// <summary>
    /// Sets the latitude for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <param name="latitude">
    /// The latitude.
    /// </param>
    public static void SetLatitude(DependencyObject dependencyObject, double latitude)
    {
        dependencyObject.SetValue(LatitudeProperty, latitude);
    }

    /// <summary>
    /// Gets the left offset for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <returns>
    /// The left offset.
    /// </returns>
    public static double GetLeft(DependencyObject dependencyObject)
    {
        return (double)dependencyObject.GetValue(LeftProperty);
    }

    /// <summary>
    /// Sets the left offset for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <param name="left">
    /// The left offset.
    /// </param>
    public static void SetLeft(DependencyObject dependencyObject, double left)
    {
        dependencyObject.SetValue(LeftProperty, left);
    }

    /// <summary>
    /// Gets the top offset for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <returns>
    /// The top offset.
    /// </returns>
    public static double GetTop(DependencyObject dependencyObject)
    {
        return (double)dependencyObject.GetValue(TopProperty);
    }

    /// <summary>
    /// Sets the top offset for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <param name="top">
    /// The top offset.
    /// </param>
    public static void SetTop(DependencyObject dependencyObject, double top)
    {
        dependencyObject.SetValue(TopProperty, top);
    }

    /// <summary>
    /// Gets the horizontal alignment for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <returns>
    /// The horizontal alignment.
    /// </returns>
    public static HorizontalAlignment GetHorizontalAlignment(DependencyObject dependencyObject)
    {
        return (HorizontalAlignment)dependencyObject.GetValue(HorizontalAlignmentProperty);
    }

    /// <summary>
    /// Sets the horizontal alignment for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <param name="horizontalAlignment">
    /// The horizontal alignment.
    /// </param>
    public static void SetHorizontalAlignment(DependencyObject dependencyObject, HorizontalAlignment horizontalAlignment)
    {
        dependencyObject.SetValue(HorizontalAlignmentProperty, horizontalAlignment);
    }

    /// <summary>
    /// Gets the vertical alignment for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <returns>
    /// The vertical alignment.
    /// </returns>
    public static VerticalAlignment GetVerticalAlignment(DependencyObject dependencyObject)
    {
        return (VerticalAlignment)dependencyObject.GetValue(VerticalAlignmentProperty);
    }

    /// <summary>
    /// Sets the vertical alignment for a specified dependency object.
    /// </summary>
    /// <param name="dependencyObject">
    /// The dependency object.
    /// </param>
    /// <param name="verticalAlignment">
    /// The vertical alignment.
    /// </param>
    public static void SetVerticalAlignment(DependencyObject dependencyObject, VerticalAlignment verticalAlignment)
    {
        dependencyObject.SetValue(VerticalAlignmentProperty, verticalAlignment);
    }

    /// <summary>
    /// Measures all child controls, imposing no restrictions on their size.
    /// </summary>
    /// <param name="availableSize">
    /// The available size.
    /// </param>
    /// <returns>
    /// The measured size.
    /// </returns>
    protected override Size MeasureOverride(Size availableSize)
    {
        availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);

        foreach (UIElement child in this.InternalChildren)
        {
            if (child != null)
            {
                child.Measure(availableSize);
            }
        }

        return new Size();
    }

    /// <summary>
    /// Arranges all child controls.
    /// </summary>
    /// <param name="finalSize">
    /// The final size.
    /// </param>
    /// <returns>
    /// The size of the content.
    /// </returns>
    protected override Size ArrangeOverride(Size finalSize)
    {
        foreach (FrameworkElement child in this.InternalChildren)
        {
            if (child == null)
            {
                continue;
            }

            var xRatio = GetXRatio(child);
            var yRatio = GetYRatio(child);
            var x = xRatio * ActualWidth;
            var y = yRatio * ActualHeight;

            switch (child.HorizontalAlignment)
            {
                case HorizontalAlignment.Center:
                    x -= child.DesiredSize.Width / 2;
                    break;
                case HorizontalAlignment.Right:
                    x -= child.DesiredSize.Width;
                    break;
            }

            switch (child.VerticalAlignment)
            {
                case VerticalAlignment.Center:
                    y -= child.DesiredSize.Height / 2;
                    break;
                case VerticalAlignment.Bottom:
                    y -= child.DesiredSize.Height;
                    break;
            }

            child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
        }

        return finalSize;
    }

    private static double GetXRatio(DependencyObject dependencyObject)
    {
        return (double)dependencyObject.GetValue(XRatioProperty);
    }

    private static void SetXRatio(DependencyObject dependencyObject, double xRatio)
    {
        dependencyObject.SetValue(XRatioProperty, xRatio);
    }

    private static double GetYRatio(DependencyObject dependencyObject)
    {
        return (double)dependencyObject.GetValue(YRatioProperty);
    }

    private static void SetYRatio(DependencyObject dependencyObject, double yRatio)
    {
        dependencyObject.SetValue(YRatioProperty, yRatio);
    }

    private static double CalculateYRelative(double latitude)
    {
        return Math.Log(Math.Tan(((latitude / 360d) * Math.PI) + (Math.PI / 4)));
    }

    private static void OnLatitudeRangeChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var reference = dependencyObject as MercatorProjectionPanel;

        if (reference != null)
        {
            reference.minY = CalculateYRelative(reference.MinLatitude);
            reference.maxY = CalculateYRelative(reference.MaxLatitude);
        }
    }

    private static void OnGeographicalCoordinateChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var reference = dependencyObject as FrameworkElement;

        if (reference != null)
        {
            var parent = VisualTreeHelper.GetParent(reference) as MercatorProjectionPanel;

            if (parent != null)
            {
                SetLeft(reference, ConvertLongitudeToX(parent, GetLongitude(reference)));
                SetTop(reference, ConvertLatitudeToY(parent, GetLatitude(reference)));

                parent.InvalidateArrange();
            }
        }
    }

    private static void OnCoordinateChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var reference = dependencyObject as FrameworkElement;

        if (reference != null)
        {
            var parent = VisualTreeHelper.GetParent(reference) as MercatorProjectionPanel;

            if (parent != null)
            {
                ////SetLongitude(reference, ConvertXToLongitude(parent, GetLeft(reference)));
                ////SetLatitude(reference, ConvertYToLatitude(parent, GetTop(reference)));

                ////parent.InvalidateArrange();

                var left = GetLeft(reference);
                var top = GetTop(reference);

                SetXRatio(reference, left / parent.ActualWidth);
                SetYRatio(reference, top / parent.ActualHeight);
            }
        }
    }

    private static double ConvertXToLongitude(MercatorProjectionPanel panel, double left)
    {
        return ((left / panel.ActualWidth) * 360) - 180;
    }

    private static double ConvertYToLatitude(MercatorProjectionPanel panel, double top)
    {
        var input = panel.maxY - ((top / panel.ActualHeight) * (panel.maxY - panel.minY));
        return Math.Atan(Math.Sinh(input)) * DegreesPerRadian;
    }

    private static double ConvertLongitudeToX(MercatorProjectionPanel panel, double longitude)
    {
        return ((longitude + 180) / 360) * panel.ActualWidth;
    }

    private static double ConvertLatitudeToY(MercatorProjectionPanel panel, double latitude)
    {
        return panel.ActualHeight - (panel.ActualHeight * (CalculateYRelative(latitude) - panel.minY) / (panel.maxY - panel.minY));
    }
}

如果我到过博客文章,我会更新这个答案。

答案 1 :(得分:0)

您还需要具有您希望画布显示的纬度/长度最小值和最大值(可以从缩放级别计算,或者您可以对它们进行硬编码)。然后你可以确定一个对象的相对坐标,如:RelX =(ObjectLong-MinLong)/(MaxLong-MinLong)和RelY =(ObjectLat-MinLat)/(MaxLat-MinLat) 然后在MeasureOverride()方法中根据相对坐标定位画布的子项。