Промяна на координатната система на Canvas в WPF

Пиша приложение за картографиране, което използва Canvas за позициониране на елементи. За всеки елемент трябва програмно да конвертирам Lat/Long на елемента в координатата на платното, след което да задам свойствата Canvas.Top и Canvas.Left.

Ако имах платно 360x180, мога ли да преобразувам координатите на платното, за да преминат от -180 към 180 вместо 0 към 360 по оста X и 90 към -90 вместо 0 към 180 по оста Y?

Изисквания за мащабиране:

  • Платното може да бъде с всякакъв размер, така че все пак трябва да работи, ако е 360x180 или 5000x100.
  • Областта Lat/Long може не винаги да е (-90,-180)x(90,180), може да е всякаква (т.е. (5,-175)x(89,-174)).
  • Елементи като PathGeometry, които са точкова основа, а не базирани на Canvas.Top/Left, трябва да работят.

person Dylan    schedule 31.10.2008    source източник


Отговори (8)


Ето изцяло 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.

РЕДАКТИРАНЕ: Можете да коригирате проблема с фона, като поставите своето платно в друго платно. Някак странно, но работи. Освен това добавих ScaleTransform, който обръща 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 вероятно ще свърши работа.

person Ryan Lundy    schedule 31.10.2008
comment
Това е доста близо до това, от което се нуждая, но не мисля, че това се мащабира достатъчно добре. Ако или координатната система е Lat=(2, -74) Lon=(-180, -175) или ако размерът на платното се промени (h=50,w=100), тя няма да работи. Освен това свойството Background е неизползваемо. - person Dylan; 01.11.2008
comment
Различните трансформации са това, което искате, но намирането на точната правилна комбинация и ред от тях е доста мъчно. - person Ryan Lundy; 01.11.2008
comment
...и това не е по-лесно от факта, че прозорецът за визуализация на XAML често не показва нещата в мащаб. Ако нещо, което трябва да е правилно, изглежда изключено в прозореца за визуализация, може да се наложи да стартирате приложението, за да видите, че не е наистина изключено. - person Ryan Lundy; 01.11.2008

Успях да го постигна, като създадох свое собствено персонализирано платно и замених функцията 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. Няма да работи, защото точките не са определени като Горна и Лява, а като действителни точки.

person Dylan    schedule 31.10.2008

Почти съм сигурен, че не можете да направите това точно, но би било доста тривиално да имате метод, който превежда от шир/дължина в координати на платно.

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

(Или нещо в този дух)

person MojoFilter    schedule 31.10.2008
comment
Имам тази функция, просто се надявах да не се налага да конвертирам точки към и от платното през цялото време. - person Dylan; 31.10.2008
comment
Правейки го по този начин има допълнителното предимство да направите вашето платно хубаво и мащабируемо. - person MojoFilter; 31.10.2008

Предполагам, че друг вариант би бил да разширите платното и да отмените мярката/подредите, за да го накарате да се държи по начина, по който искате.

person MojoFilter    schedule 31.10.2008
comment
Това е по-скоро в смисъла, който си мислех, но не съм сигурен как да го приложа. - person Dylan; 31.10.2008
comment
Всъщност не е многоооо просто. Идеята е да замените ArrangeOverride() и MeasureOverride(). - person MojoFilter; 31.10.2008

Можете да използвате transform за превод между координатните системи, може би TransformGroup с TranslateTranform за преместване (0,0) в центъра на платното и ScaleTransform за получаване на координатите в правилния диапазон.

С обвързване на данни и може би преобразувател на стойност или два можете да накарате трансформациите да се актуализират автоматично въз основа на размера на платното.

Предимството на това е, че ще работи за всеки елемент (включително PathGeometry), възможен недостатък е, че ще мащабира всичко, а не само точки - така че ще промени размера на иконите и текста на картата.

person Nir    schedule 02.11.2008

Друго възможно решение:

Вградете персонализирано платно (платно за изчертаване) в друго платно (платно за фон) и задайте платното за изчертаване така, че да е прозрачно и да не се изрязва в границите. Трансформирайте рисуваното платно с матрица, която прави y обръщане (M22 = -1) и премества/мащабира платното вътре в родителското платно, за да видите разширението на света, който гледате.

Всъщност, ако рисувате при -115, 42 в платното за рисуване, елементът, който рисувате, е "извън" платното, но въпреки това се показва, защото платното не се изрязва до границите. След това преобразувате рисуваното платно, така че точката да се показва на правилното място на фоновото платно.

Това е нещо, което скоро ще опитам сам. Дано помогне.

person FTLPhysicsGuy    schedule 18.06.2010
comment
Забележка: Опитах това, но открих една странност. Ако мащабирате, докато един метър стане приблизително няколко десетки пиксела и НЕ сте близо до действителната 0,0 точка на платното за рисуване, преместването на платното за рисуване (чрез трансформиране ИЛИ чрез Canvas.SetTop/SetLeft) изглежда да се квантува. Въпреки че променяте горе/ляво или M11/M22 с малки стойности, визуалният чертеж не се движи освен при квантувани стойности (не съм сигурен какви са тези стойности). Чудя се дали прави изчисления в двойно, но ги предава на някаква рутина от по-ниско ниво, която преобразува в плаващи числа, като по този начин квантува големи числа. - person FTLPhysicsGuy; 23.06.2010

Ето отговор, който описва Canvas метод за разширение, който ви позволява да приложите декартова координатна система. т.е.:

canvas.SetCoordinateSystem(-10, 10, -10, 10)

ще зададе координатната система на canvas така, че x да преминава от -10 към 10 и y да преминава от -10 към 10.

person dharmatech    schedule 25.11.2012

имам почти същия проблем. така че отидох онлайн. и този човек използва матрица, за да се трансформира от „пиксел на устройството“ в това, което той нарича „световни координати“ и под това има предвид числа от реалния свят вместо „пиксели на устройството“, вижте връзката

http://csharphelper.com/blog/2014/09/use-transformations-draw-graph-wpf-c/

person Morten Brask Jensen    schedule 14.12.2016