none
TranslateTransform for map coordinates? RRS feed

  • Question

  • Hi

    I'm writing an application where I want to render Map features (objects) to a canvas. What I want to do is be able to create Shapes using map coordinates for all points, and have WPF convert them into screen coordinates using a transform.

    I have used the following code from http://www.c-sharpcorner.com/UploadFile/nschan/ShapeFile02252007134834PM/ShapeFile.aspx to create an TransformGroup containing a ScaleTransform and TranslateTransform

    private TransformGroup CreateShapeTransform(ShapeFileReadInfo info) 
     
        // Bounding box for the shapefile. 
        double xmin = info.ShapeFile.FileHeader.XMin; 
        double xmax = info.ShapeFile.FileHeader.XMax; 
        double ymin = info.ShapeFile.FileHeader.YMin; 
        double ymax = info.ShapeFile.FileHeader.YMax; 
     
        // Width and height of the bounding box. 
     
        double width = Math.Abs(xmax - xmin); 
        double height = Math.Abs(ymax - ymin); 
     
        // Aspect ratio of the bounding box. 
        double aspectRatio = width / height; 
     
        // Aspect ratio of the canvas. 
        double canvasRatio = this.canvas.ActualWidth / this.canvas.ActualHeight; 
     
        // Compute a scale factor so that the shapefile geometry 
        // will maximize the space used on the canvas while still 
        // maintaining its aspect ratio. 
     
        double scaleFactor = 1.0; 
        if (aspectRatio < canvasRatio) 
            scaleFactor = this.canvas.ActualHeight / height; 
        else 
            scaleFactor = this.canvas.ActualWidth / width; 
     
        // Compute the scale transformation. Note that we flip 
        // the Y-values because the lon/lat grid is like a cartesian 
        // coordinate system where Y-values increase upwards. 
        ScaleTransform xformScale = new ScaleTransform(scaleFactor, -scaleFactor); 
     
        // Compute the translate transformation so that the shapefile 
        // geometry will be centered on the canvas. 
        TranslateTransform xformTrans = new TranslateTransform(); 
        xformTrans.X = (this.canvas.ActualWidth - (xmin + xmax) * scaleFactor) / 2; 
        xformTrans.Y = (this.canvas.ActualHeight + (ymin + ymax) * scaleFactor) / 2; 
     
        // Add the two transforms to a transform group. 
        TransformGroup xformGroup = new TransformGroup(); 
        xformGroup.Children.Add(xformScale); 
        xformGroup.Children.Add(xformTrans); 
        return xformGroup; 

    using xformGroup.Transform and xformGroup.Inverse.Transform I am able to convert a screen coordinate into a map coordinate which seems to be corret, as well a map coordinate within the bounding box to a screen coordinate.

    My hope is that I can create a polygon shape using System.Windows.Point with map coordinates as X and Y values. Then somehow apply the TransformGroup to a LayoutTransform or RenderTransform and have WPF render them to the correct screen coordinate based on the transform. When doing this, the shape is not being rendered to the screen. Is there something I'm doing wrong, or is my goal not feasible?

    The reason I want to do this is so that when I pan or zoom, I can just update the TransformGroup instead of having to convert all the map coordinates to screen coordinates on every pan/zoom.

    Best regards

    Bjørnar

    Tuesday, October 28, 2008 8:02 PM

All replies

  • -> My hope is that I can create a polygon shape using System.Windows.Point with map coordinates as X and Y values. Then somehow apply the TransformGroup to a LayoutTransform or RenderTransform and have WPF render them to the correct screen coordinate based on the transform. When doing this, the shape is not being rendered to the screen. Is there something I'm doing wrong, or is my goal not feasible?

    It's feasible, could you please provide a small, complete and ready-to-run example to demonstrate how it doesn't work for you?

    Thanks
    Friday, October 31, 2008 7:05 AM
  • The transform is updated every time the map is zoomed, panned or resized. When the window is loaded, a ScaleTransform and TranslateTransform are created and added to a TransformGroup.

    LayerHost is responsible for rendering features (objects being displayed in the map). Background brush is just to see what is where (is the LayerHost rendered). I want to apply the transform to LayerHost so all children are rendered at the correct location on the screen. For this sample, _mapControl is just a container for the features being rendered.

    When applying the transformation to RenderTransform, LayerHost is not rendered (offsett is probably a bit off). Applying it to LayoutTransform, LayerHost is rendered as a small canvas in the top left corner.

    Clicking in the map will add an ellipse to LayerHost. The position of the ellipse transformed from the active "TransformType" set on the LayerHost. Change the transform scheme on the LayerHost by setting the value of the FeatureTransformType property to either Render, Layout or None.

    When set to None, an ellipse is added to the canvas using screen coordinates. When Render or Layout, the ellipse should be added to the canvas using "world coordinates" transformed by the TransformGroup, but still rendered where the mouse is clicked on the screen.

    I want apply the TransformGroup to the LayerHost, but I don't want it to affect the layout of the LayerHost itself (it should fill it's parent), but to its children and MouseButtonEventArgs and any calls to Mouse.GetPosition() where a LayerHost instance is passed as argument.

    XAML

    <Window x:Class="MapTransform.Window1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:MapTransform="clr-namespace:MapTransform" 
        Title="Window1" Height="550" Width="550">  
        <Canvas x:Name="_mapControl" Background="SaddleBrown">  
            <MapTransform:LayerHost FeatureTransformType="None" x:Name="_layerHost" Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}" Height="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}" Background="Beige">  
     
            </MapTransform:LayerHost> 
        </Canvas> 
    </Window> 

    C#

    using System;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Media;  
    using System.Windows.Shapes;  
     
    namespace MapTransform  
    {  
        /// <summary>  
        /// Interaction logic for Window1.xaml  
        /// </summary>  
        public partial class Window1 : Window  
        {  
            private TranslateTransform _translateTransform;  
            private readonly TransformGroup _transformGroup = new TransformGroup();  
            private ScaleTransform _scaleTransform;  
            private Extent _extent;  
     
            public Window1()  
            {  
                InitializeComponent();  
                SetupTransform();  
            }  
     
            private void SetupTransform()  
            {  
                _extent = new Extent(60000, 60000, 100000, 100000);  
                _scaleTransform = new ScaleTransform();  
                _translateTransform = new TranslateTransform();  
                _transformGroup.Children.Add(_scaleTransform);  
                _transformGroup.Children.Add(_translateTransform);  
                _layerHost.FeaturTransform = _transformGroup;  
            }  
     
            protected override void OnMouseUp(System.Windows.Input.MouseButtonEventArgs e)  
            {  
                base.OnMouseUp(e);  
                Point point = e.GetPosition(_mapControl);  
                Point position = _layerHost.FeaturTransform.Inverse.Transform(point);  
                _layerHost.AddFeatureAtPosition(position);  
            }  
     
            protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)  
            {  
                base.OnRenderSizeChanged(sizeInfo);  
                UpdateTransform();  
            }  
     
            public void UpdateTransform()  
            {  
                double xmin = _extent.MinE;  
                double xmax = _extent.MaxE;  
                double ymin = _extent.MinN;  
                double ymax = _extent.MaxN;  
     
                // Width and height of the bounding box.  
                double width = Math.Abs(xmax - xmin);  
                double height = Math.Abs(ymax - ymin);  
     
                // Aspect ratio of the bounding box.  
                double aspectRatio = width / height;  
     
                // Aspect ratio of the canvas.  
                double canvasRatio = ActualWidth / ActualHeight;  
     
                // Compute a scale factor so that the shapefile geometry  
                // will maximize the space used on the canvas while still  
                // maintaining its aspect ratio.  
                double scaleFactor = 1.0;  
                if (aspectRatio < canvasRatio)  
                    scaleFactor = ActualHeight / height;  
                else 
                    scaleFactor = ActualWidth / width;  
     
                // Compute the scale transformation. Note that we flip  
                // the Y-values because the lon/lat grid is like a cartesian  
                // coordinate system where Y-values increase upwards.  
                _scaleTransform.ScaleX = scaleFactor;  
                _scaleTransform.ScaleY = -scaleFactor;  
     
                // Compute the translate transformation so that the shapefile  
                // geometry will be centered on the canvas.  
                double x = ((ActualWidth - (xmin + xmax) * scaleFactor) / 2);  
                double y = ((ActualHeight + (ymin + ymax) * scaleFactor) / 2);  
                _translateTransform.X = x;  
                _translateTransform.Y = y;  
            }  
                      
            public class Extent  
            {  
                public Extent(double minE, double minN, double maxE, double maxN)  
                {  
                    MinE = minE;  
                    MinN = minN;  
                    MaxE = maxE;  
                    MaxN = maxN;  
                }  
     
                public double MinE { getset; }  
                public double MinN { getset; }  
                public double MaxE { getset; }  
                public double MaxN { getset; }  
            }  
        }  
     
        public class LayerHost : Canvas  
        {  
            /// <summary>  
            /// Add an ellipse to the canvas using world coordinate  
            /// </summary>  
            /// <param name="position"></param>  
            public void AddFeatureAtPosition(Point position)  
            {  
                var shape = new Ellipse  
                {  
                    Fill = new SolidColorBrush(Colors.Black),  
                    Width = 20,  
                    Height = 20  
                };  
                Children.Add(shape);  
                SetLeft(shape, position.X);  
                SetTop(shape, position.Y);  
            }  
     
            private Transform _featurTransform;  
            public Transform FeaturTransform  
            {  
                get 
                {  
                    if(FeatureTransformType == TransformType.None)  
                    {  
                        return Transform.Identity;  
                    }  
       
                    return _featurTransform;  
                }  
                set 
                {  
                    _featurTransform = value;  
                    switch (FeatureTransformType)  
                    {  
                        case TransformType.Render:  
                            RenderTransform = value;  
                            break;  
                        case TransformType.Layout:  
                            LayoutTransform = value;  
                            break;  
                    }  
                }  
            }  
              
     
            public TransformType FeatureTransformType { getset; }  
        }  
        public enum TransformType { None, Render, Layout }  
    }  
     




    I hope this helps clearify what I want to accomplish. Thanks in advance

    Regards
    Bjørnar

    Friday, October 31, 2008 10:22 PM
  • You are actually trying to mess up object coordinate space used by LayerHost with the model or map coordinates you used for the map data, they are trying to bring them into a single coordinate space, unfortunately, this is either troublesome and not workable, I strongly recommend you to separate those two coordinate spaces, and you could simply directly manipulate the object coordinate space of LayoutHost when adding features (aka Ellipses or other Shapes), and when you need to save the map data, you could kick off the transformation you updated which will transform the coordinates from LayerHost's object space into the map's coordinate space (you already knows how to setup and update this transformation matrix).

    And when you need to insert any new features into the map whose coordinates are measured in map space, then you could try the inverse matrix of the aforementioned object-to-map matrix, and use this matrix to transform the features into object space, then kicking off Canvas.SetLeft() and Canvas.SetTop() to position it at the desired location.

    This essence of this design is that whenever you update the object space, you need to update the map space, this seems tedious, but it seems that it's worth it.

    Hope this helps
    • Edited by Marco Zhou Monday, November 3, 2008 8:43 AM bad typo
    Monday, November 3, 2008 8:42 AM
  • You mean I should for instance add a feature to layerhost where the feature has map coordinates, and use the inverse transform to convert from map coordinates to screen coordinates?

    This is what I have done previously, however then I have to iterate all features and convert their points every time a feature is moved (by underlying mechanisms) or whenever I pan or zoom the map. My thought was to avoid that by using a transform on the control.

    I realize that if I don't convert between map and screen coordinates, someone has to do it (read WPF). From the sample I posted, the LayerHost became small, which I assume is because the transform is applied to LayerHost. WPF apparently assume height and width units set on that control is in map coordinates, and transform these units to screen units which results in a small number. This makes sense, but I was hoping for a way to tell WPF to "Apply this transform to all children of LayerHost as they are added". Will this result in an unecessary overhead and make the "manual conversion" perform better?

    From the documentation of the LayoutTransform property (underlined):
    Setting a transform provides powerful capabilities of scaling and rotating. However, LayoutTransform ignores TranslateTransform operations. This is because the layout system behavior for child elements of a FrameworkElement auto-corrects any offsets to the position of a scaled or rotated element into the layout and coordinate system of the parent element.

    Based on this remark, TranslateTransform should be ignored, which does not seem to be the case here (I haven't tried removing the TranslateTransform from the TransformGroup to check).

    I haven't found a way to do what they describe in the following remark, which is what I want to do (underlined):
    LayoutTransform can lead to poor application performance if you invoke it in a scenario that does not require a full pass by the layout system. When you apply a LayoutTransform to the Children collection of the Panel, it triggers a new pass by the layout system and forces all on-screen objects to be remeasured and rearranged

    If what I want to do leads to poor performance, I suppose using the manual approach would be best, as you recommend.
    Monday, November 3, 2008 5:37 PM