Я пишу картографическое приложение, которое использует Canvas для позиционирования элементов. Для каждого элемента мне нужно программно преобразовать широту / долготу элемента в координату холста, а затем установить свойства Canvas.Top и Canvas.Left.
Если бы у меня был холст 360x180, могу ли я преобразовать координаты на холсте в диапазоне от -180 до 180, а не от 0 до 360 по оси X и от 90 до -90, а не от 0 до 180 по оси Y?
Требования к масштабированию:





Я почти уверен, что вы не можете сделать это точно, но было бы довольно тривиально иметь метод, который переводится с широты / долготы в координаты Canvas.
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);
}
(Или что-то вдоль этих линий)
Такой способ дает дополнительное преимущество - делает ваш холст красивым и масштабируемым.
Я предполагаю, что другим вариантом было бы расширить холст и переопределить меру / упорядочение, чтобы заставить его вести себя так, как вы хотите.
Это больше похоже на то, о чем я думал, но я не уверен, как это реализовать.
Это не совсем так просто. Идея состоит в том, что вы переопределяете ArrangeOverride () и MeasureOverride ().
Я смог добиться этого, создав свой собственный холст и переопределив функцию 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. Это не сработает, потому что точки определены не как верхняя и левая, а как фактические.
Вот решение, полностью использующее 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, вероятно, поможет.
Это довольно близко к тому, что мне нужно, но я не думаю, что это достаточно хорошо масштабируется. Если система координат была Lat = (2, -74) Lon = (- 180, -175) или если размер холста изменился (h = 50, w = 100), это не сработает. Кроме того, свойство Background невозможно использовать.
Вам нужны различные преобразования, но найти точную правильную комбинацию и порядок их довольно сложно.
... и это не упрощается тем фактом, что в окне предварительного просмотра XAML часто не отображаются элементы для масштабирования. Если что-то, что должно быть правильным, не отображается в окне предварительного просмотра, вам, возможно, придется запустить приложение, чтобы убедиться, что на самом деле это не так.
Вы можете использовать преобразование для перевода между системами координат, возможно, TransformGroup с TranslateTranform для перемещения (0,0) в центр холста и ScaleTransform, чтобы получить координаты в правильном диапазоне.
С помощью привязки данных и, возможно, одного или двух преобразователей значений вы можете настроить автоматическое обновление преобразований в зависимости от размера холста.
Преимущество этого заключается в том, что он будет работать для любого элемента (включая PathGeometry), возможный недостаток в том, что он будет масштабировать все, а не только точки, поэтому он изменит размер значков и текста на карте.
Другое возможное решение:
Вставьте настраиваемый холст (холст для рисования) в другой холст (фоновый холст) и настройте холст для рисования так, чтобы он был прозрачным и не ограничивался рамками. Преобразуйте холст, в который отрисовывается, с помощью матрицы, которая заставляет y переворачиваться (M22 = -1) и переводит / масштабирует холст внутри родительского холста, чтобы увидеть протяженность мира, на который вы смотрите.
Фактически, если вы рисуете на -115, 42 на холсте для рисования, элемент, который вы рисуете, находится «вне» холста, но все равно отображается, потому что холст не обрезается до границ. Затем вы трансформируете холст, в который можно рисовать, так, чтобы точка отображалась в нужном месте на фоновом холсте.
Я скоро попробую это сделать. Надеюсь, это поможет.
Примечание: я пробовал это, но обнаружил одну странность. Если вы увеличиваете масштаб до тех пор, пока метр не составит примерно несколько десятков пикселей, и вы НЕ приблизитесь к фактической точке 0,0 на холсте для рисования, перемещение холста для рисования (через преобразование ИЛИ через Canvas.SetTop / SetLeft) кажется быть квантованным. Даже если вы измените либо верхнее / левое положение, либо M11 / M22 на небольшие значения, визуальный рисунок не перемещается, за исключением «квантованных» значений (не уверен, что это за значения). Мне интересно, выполняет ли он вычисления в двойном режиме, но передает их некоторой подпрограмме нижнего уровня, которая преобразует в числа с плавающей запятой, таким образом «квантуя» большие числа.
Вот ответ, который описывает метод расширения Canvas, который позволяет вам применять декартову систему координат. То есть:
canvas.SetCoordinateSystem(-10, 10, -10, 10)
установит систему координат canvas так, чтобы x шел от -10 до 10, а y - от -10 до 10.
у меня почти такая же проблема. так что я зашел в интернет. и этот парень использует матрицу для преобразования из «пикселя устройства» в то, что он называет «мировыми координатами», и под этим он имеет в виду реальные мировые числа вместо «пикселей устройства» см. ссылку
http://csharphelper.com/blog/2014/09/use-transformations-draw-graph-wpf-c/
У меня есть эта функция, я просто надеялся, что мне не нужно постоянно конвертировать точки на холст и обратно.