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



Требование по качеству легко достижимо при использовании векторной графики. И тут мне пришла идея использовать 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. Не самая тривиальная задача – получение интерактивной и “резиновой” (т.е. устойчивой к изменению размеров) карты – свелась к использованию утилиты и “прикручиванию” стилей. Очень показательно, на мой взгляд.

Исходный код примера