Pull to refresh

Рисуем карту в WPF-приложении

Reading time4 min
Views26K
Недавно возникла производственная необходимость – отобразить на форме карту России, с детализацией до регионов. При этом нужно:
  • в зависимости от различных условий менять внешний вид областей карты;
  • карта должна “воспринимать” пользовательский ввод и реагировать на него;
  • нужно иметь хорошее качество “картинки-карты” вне зависимости от разрешения монитора.



Требование по качеству легко достижимо при использовании векторной графики. И тут мне пришла идея использовать WPF — а что, нарисовать картинку в Expression Blend, тем самым получив набор WPF-контролов. Остальное — добавить «интерактив» — дело техники.
Итак, самое первое, что нужно было сделать – получить собственно изображение. Страна у нас большая, что разумеется, прекрасно, но рисовать самому столько элементов не было никакого желания. Это и не потребовалось – в викимедии нашелся отличный образец карты России:
Карта из викимедии
Это как раз то, что нужно! Один момент – файл формата SVG. Нужно было как-то преобразовать его в XAML. В результате поиска набрёл на комплект XamlTune, в котором имеется консольная утилита с говорящим именем svg2xaml. Она отлично справилась со своей задачей, и вскоре я получил канву с множеством вложенных элементов Path, на которой изображена наша страна. Полученный XAML выглядит следующим образом:
<Canvas x:Key="svg2" Width="1091.9919" Height="630.11902" ClipToBounds="True">
<Canvas Name="State_Outline">
     <Path Stroke="#FF000000" StrokeMiterLimit="4" Name="polygon81">
            <Path.Data>
                 <PathGeometry FillRule="Nonzero" Figures="M715.671,134.212L715.368,133.002 717.637,134.061 718.771,132.926 720.359,131.338 722.704,128.994 724.141,130.733 724.897,130.733 726.787,128.843 728.527,126.801 731.854,125.289 734.274,127.104 732.913,130.885 732.913,133.002 731.325,134.59 728.829,135.725 726.107,136.784 723.385,136.784 721.343,138.826 718.091,137.691 716.427,135.271 715.671,134.212z" />
            </Path.Data>
     </Path>
<!-- ... -->
</Canvas>
<!-- ... -->
</Canvas>

Очевидно, используется несколько экземпляров канвы, для отрисовки границ государства и субъектов.
Дело за малым – осталось вывести карту на экран. Казалось бы, всё очень просто – помещаем канву на форму и наслаждаемся. Так-то оно так, только можно заметить её весьма солидные размеры: Width=«1091.99» Height=«630.119». Как правильно её масштабировать? Если заменить канву на Grid, например, при изменении размера формы все части карты “разъезжаются”. Чтобы этого избежать, воспользуемся элементом управления Viewbox. Разместим его на форме следующим образом:
<Viewbox Child="{StaticResource svg2}" Stretch="Uniform" />

Здесь стоит отметить, что я поместил канву с картой в ресурсы с ключом “svg2”. Канва будет сжата с сохранением пропорций до текущих размеров контейнера ViewBox. При этом абсолютные координаты в пределах канвы останутся действительными, и верстка карты не “поедет”. Теперь можно в стиле задать обработчик нажатия кнопки мыши, например так:
<Style TargetType="{x:Type Path}">
      <EventSetter Event="MouseDown" Handler="mouseDownHandler"/>
</Style>

Т.о. будет задан обработчик нажатия для всех элементов Path. Ну и т.д.- в стиле можно определить триггеры, задать свойства, тем самым обеспечив необходимый внешний вид и поведение: дальнейшие действия зависят уже от ваших задач и фантазии. Например, можно выделять границы региона при наведении мыши, или подсвечивать фон при наступлении какого-либо события и др.
Мне нужна была интерактивная карта, т.е. чтобы можно было работать с её отдельными частями, в т.ч. принимая пользовательский ввод. Если же такого не требуется, можно использовать канву с картой в качестве VisualBrush. Сделать это можно, например, так:
<Window x:Class="WpfApplication46.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300">
<Window.Resources>
    <Canvas x:Key="svg2" Width="1091.99" Height="630.119">        
        <!-- ...здесь сама карта... -->    
    </Canvas>
</Window.Resources>
<Grid>
    <Border Margin="20">         
        <Border.Background>             
            <VisualBrush Stretch="Uniform" Visual="{StaticResource svg2}"/>         
        </Border.Background>     
    </Border> 
</Grid>
</Window>

Чтобы сохранить оригинальные соотношения сторон канвы с картой, было задано значение Uniform для свойства Stretch кисти. Разумеется, в случае применения VisualBrush мы получим выигрыш в быстродействии – потребуется меньше ресурсов CPU и оперативной памяти. В подтверждение этого — данные от профилировщика из комплекта Visual Studio 2010 (синяя линия графика – загрузка CPU):
1) Вариант с VisualBrush (выделение managed-памяти ~11,36Mb):
image
2) Вариант с ViewBox (выделение managed-памяти ~14,83Mb):
image
Поэтому если обработка пользовательского ввода не нужна, подход c VisualBrush является более предпочтительным.

Вот так, довольно просто и быстро решить задачу работы с географической картой. Именно в таких ситуациях начинаешь понимать всю мощь технологии WPF. Не самая тривиальная задача – получение интерактивной и “резиновой” (т.е. устойчивой к изменению размеров) карты – свелась к использованию утилиты и “прикручиванию” стилей. Очень показательно, на мой взгляд.

Исходный код примера
Tags:
Hubs:
+28
Comments25

Articles