在WPF中更改Canvas的坐标系

18

我正在编写一个使用Canvas定位元素的地图应用程序。对于每个元素,我都需要将该元素的纬度/经度编程转换为Canvas上的坐标,然后设置Canvas.Top和Canvas.Left属性。

如果我有一个360x180的Canvas,我能否将Canvas上的坐标从X轴上的0到360转换为-180到180,而将Y轴上的0到180转换为90到-90?

缩放要求:

  • Canvas可以是任何尺寸,因此即使它是360x180或5000x100,也应该能正常工作。
  • 纬度/经度区域可能不总是(-90,-180)x(90,180),它可能是任何东西(例如(5,-175)x(89,-174))。
  • 像PathGeometry这样基于点而不是基于Canvas.Top/Left的元素也需要正常工作。
8个回答

6

以下是一种完全使用XAML的解决方案。虽然大部分都是XAML,但你需要在代码中添加IValueConverter。具体步骤如下:创建一个新的WPF项目,并添加一个类。这个类叫做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();
        }
    }
}

然后在你的窗口中使用此XAML。现在,您应该可以在XAML预览窗口中看到结果。

编辑: 您可以通过将您的Canvas放在另一个Canvas内来解决背景问题。有点奇怪,但它有效。另外,我添加了一个ScaleTransform,它翻转Y轴,使正Y向上,负Y向下。请仔细注意名称的位置:

<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>

关于您的新需求,需要不同范围的数值,一个更复杂的ValueConverter可能会解决问题。


这很接近我所需的,但我认为它的可扩展性不够好。如果坐标系是Lat=(2, -74) Lon=(-180, -175),或者Canvas的大小发生变化(h=50,w=100),它将无法工作。此外,Background属性也无法使用。 - Dylan
各种变换是你想要的,但找到它们的确切组合和顺序有点麻烦。 - Ryan Lundy
...而且由于XAML预览窗口经常不能按比例显示,这使得情况变得更加棘手。如果在预览窗口中某些应该正确的东西看起来不对劲,你可能需要运行程序以查看它是否真正存在问题。 - Ryan Lundy

1

我通过创建自己的自定义画布并覆盖ArrangeOverride函数来实现它:

    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);
        }
    }

这适用于我描述的问题,但对于我另一个需要(PathGeometry),它可能不起作用。因为这些点不是定义为上和左,而是实际的点。


0

我相信你无法完全做到这一点,但将经纬度转换为画布坐标的方法非常简单。

Point ToCanvas(double lat, double lon) {
  double x = ((lon * myCanvas.ActualWidth) / 360.0) - 180.0;
  double y = ((lat * myCanvas.ActualHeight) / 180.0) - 90.0;
  return new Point(x,y);
}

或类似的内容


我有那个函数,只是希望不需要一直在画布和点之间进行转换。 - Dylan
以那种方式做还有一个额外的好处,就是使你的画布更加美观和可扩展。 - MojoFilter

0

0
这里有一个答案,它描述了一个Canvas扩展方法,允许您应用笛卡尔坐标系。即:
canvas.SetCoordinateSystem(-10, 10, -10, 10)

将设置画布的坐标系,使得x范围从-10到10,y范围从-10到10。


0

另一个可能的解决方案:

在另一个画布(背景画布)中嵌入自定义画布(绘制画布),并将绘制画布设置为透明且不剪切到边界。使用矩阵对绘制画布进行变换,使其y轴翻转(M22 = -1),并在父画布内平移/缩放画布以查看您正在查看的世界范围。

实际上,如果您在绘制画布中绘制-115、42,则您正在绘制的项目“离开”了画布,但仍会显示出来,因为画布没有剪切到边界。然后,您可以对绘制画布进行变换,以便在背景画布上正确显示该点。

这是我很快就要尝试的事情。希望它有所帮助。


注意:我试过这个,但发现一个奇怪的现象。如果你将比例尺缩小到大约几十像素,而你不在绘制画布的实际0,0点附近,移动绘制画布(通过 transform 或 Canvas.SetTop/SetLeft)似乎是量化的。即使你通过小值更改顶部/左侧或 M11/M22,视觉绘图也只在“量化”值处移动(不确定那些值是什么)。我想知道它是否在双精度计算,但将它们传递给一些低级别的例程进行转换为浮点数,因此“量化”大数。 - FTLPhysicsGuy

0
我想另一个选择是扩展画布并覆盖测量/排列以使其按照您的要求运行。

这更符合我的想法,但我不确定如何实现它。 - Dylan
这并不是非常简单的。思路是你需要重写ArrangeOverride()和MeasureOverride()方法。 - MojoFilter

0

您可以使用Transform在坐标系之间进行转换,可能需要使用一个TransformGroup和TranslateTranform将(0,0)移动到画布的中心,并使用ScaleTransform将坐标范围调整到正确的范围。

通过数据绑定和一两个值转换器,您可以使变换根据画布大小自动更新。

这样做的优点是它适用于任何元素(包括PathGeometry),可能的缺点是它会缩放所有内容而不仅仅是点 - 因此它会改变地图上图标和文本的大小。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接