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

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



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

    Исходный код примера
    Поделиться публикацией

    Похожие публикации

    Комментарии 25

      0
      а где посмотреть рабочий пример?
        0
        Ссылка на архив с исходником — в самом конце статьи
          –4
          я нашел. вы предлагаете скачать, распаковать и посмотреть?
            +5
            Это уже вам решать :)
              0
              я может чего не понимаю, но ведь можно это где-то выложить в рабочем виде?
                0
                Простите, но теперь я вас не понимаю — вы хотели, чтобы я выложил скомпилированный exe? Поясните, что значит «в рабочем виде»
                  +1
                  а теперь понял. это не для веба. сорри за тупняк
        +2
        Да, мощно, и ни одной строчки кода (=
          +1
          боюсь весь приведенный xaml-код переписанный на C# хуже читался бы. На то xaml и создавался
            +1
            Да, я знаю xaml, за это и люблю WPF.
          +4
          Вообще прикол в том что SVG и XAML это один и тот же формат :) например, конверсия из PathGeometry в SVG примитивна — где-то 10 строк кода или около того.
            0
            VItus, скажите пожалуйста, может ли wpf качественно и быстро отрисовывать большие карты (со множеством объектов: реки, дома, дороги и прочее, большие полигоны по 20к точек).

            С уважением, Kets.
              0
              Можно использовать MultiScaleImage(DeepZoom), но он работает только в Silverlight'e. Для WPFа есть вот такой костыль, на сколько быстро он работает не знаю, но DeepZoom работает очень хорошо!
                +2
                Дипзуум не отрисовывает растр из вектора, он просто выводит растр и осуществляет зуум между изображениями разного масштабами…

                Отрисовка же через WPF очень медленная(если брать тысячи полигонов), даже если поддерживается ускорение.
                  0
                  а у кого отрисовка быстрая?
                    0
                    Если грамотно отсекать невидимые объекты, то можно спокойно работать с детализованной до дома картой города-милионнера.

                    Мне это удавалось. Было около 16000 строений + 1000 улиц.
                      0
                      это называется ГИС :)
                0
                Kets, признаться, с такими полигонами в WPF не сталкивался. Подозреваю, что работать всё-таки будет не слишком быстро :) Хотя может и здесь есть свои «трюки»
                  0
                  А откуда у вас в полигонах по 20к точек? Это что за объект такой, если не секрет?
                    0
                    Это мультиполигон в Mapinfo, ну и плюс по 10к в полигонах регионов РФ.
                  +1
                  Просто у нас на работе как-то попробовали поэксперементировать с WPF, отрисовывает-то он быстро, а вот зум и передвижение — сильно подтупливали. Вот мне и стало интересно спросить у знающего человека. Пробовали на 4-м .NET'е (вроде MS туда добавила кэширование в битмап) — в итоге 4Гб памяти были съедены :)
                  Поэтому создалось впечатление, что WPF подходит для отрисовки небольшого количества объектов.

                  Поэтому остались пока на managed direct x.

                  И спасибо все за ответы.
                    0
                    MDX мертв, советую обратить внимание на SlimDX
                      0
                      Столкнулись с аналогичной проблемой, когда делали интерактивную карту на WPF (крутили в 3D).
                      При запуске анимации зуммирования, иногда появлялись рандомные лаги размером ~ в секунду.
                      Такое ощущение что в этот момент движек дергал какую-то ресурсоемкую функцию.

                      Тогда с первых двух попыток отловить причину так и не удалось.
                      Теоретически, можно конечно поиграться с отпимизациями которые реокмендует майкрософт… — сделать например всему Freeze(), итп (тогда до этого руки так и не дошли).

                      Но в целом ощущение что в какой-то момент действительно упираешся в возможности движка WPF по количеству объектов. Все-таки оно не для слишком сложных сцен. Для навороченой графики MS рекомендует XNA. :]
                      +1
                      Кому интересно, я недавно выкладывал здесь нечто подобное для карты Мира:
                      habrahabr.ru/blogs/silverlight/64911/
                        0
                        Может кому понадобится, карта мира в XAML:
                        sgolubev.com/blog/2009/09/24/world-map-in-xaml/

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое