C#, Визуализация

Работа с картами. GMap C#

Время прочтения: 7 мин.

На написание данной статьи меня подтолкнуло не столь большое наличие текстовой информации по работе с GMap на просторах интернета, как хотелось бы.

Стояла задача визуализировать на карте положение объектов по известным координатам для дальнейшего анализа и выявления каких-либо закономерностей расположения этих объектов.

Первое что могу порекомендовать из своего опыта – начать работать сразу с WinForm, а не с WPF т.к. проще и больше информации можно добыть именно по ней.

Краткий словарь терминов, которые встретятся в моей статье:

  • Тайловая, плиточная или знакоместная графика (от англ. tile — плитка) — метод создания больших изображений (как правило, уровней в компьютерных играх) из маленьких фрагментов одинаковых размеров
  • Тайл — элемент разбиения
  • Слой можно сравнить с прозрачной пленкой, наложенной на изображение, на который наносятся пользовательские элементы
  • Тепловая карта — графическое представление данных, где индивидуальные значения в таблице отображаются при помощи цвета. Термин «heatmap» изначально был придуман и официально зарегистрирован как товарный знак разработчиком программного обеспечения Кормаком Кинни в 1991 году.
  • Полигон — многоугольник, минимальная поверхность для визуализации в трёхмерной графике.
  • Маркер — отметка на карте

Переходим к работе с библиотекой.

Нам понадобятся GMap.NET.Core.dll и GMap.NET.WindowsForms.dll Для установки можно использовать Nuget.

или скачать dll из интернета и вставить ссылки

Щелкните правой кнопкой мыши панель инструментов и нажмите «Выбрать элементы…». В появившемся окне с помощью кнопки «Обзор» найдите файл GMap.NET.WindowsForms.dll:

После закрытия окна новый элемент управления появится в наборе инструментов. Перетаскиваем его в форму и настраиваем положение и размеры.

Создаем эвент (событие) загрузки контрала.

Вставляем код:

GMap.NET.GMaps.Instance.Mode = GMap.NET.AccessMode.ServerAndCache; //выбор подгрузки карты – онлайн или из ресурсов
            gMapControl1.MapProvider = GMap.NET.MapProviders.GoogleMapProvider.Instance; //какой провайдер карт используется (в нашем случае гугл) 
            gMapControl1.MinZoom = 2; //минимальный зум
            gMapControl1.MaxZoom = 16; //максимальный зум
           gMapControl1.Zoom = 4; // какой используется зум при открытии
            gMapControl1.Position = new GMap.NET.PointLatLng(66.4169575018027, 94.25025752215694);// точка в центре карты при открытии (центр России)
            gMapControl1.MouseWheelZoomType = GMap.NET.MouseWheelZoomType.MousePositionAndCenter; // как приближает (просто в центр карты или по положению мыши)
            gMapControl1.CanDragMap = true; // перетаскивание карты мышью
            gMapControl1.DragButton = MouseButtons.Left; // какой кнопкой осуществляется перетаскивание
            gMapControl1.ShowCenter = false; //показывать или скрывать красный крестик в центре
            gMapControl1.ShowTileGridLines = false; //показывать или скрывать тайлы

Результат выполнения кода. Карта делится на квадраты, называемые тайлами. Тайлы и крест по центру включены.

Работа с библиотекой. Маркеры

Чтобы отобразить объект на карте мы создаем маркер с координатами объекта и выводим его на карту. Теперь создадим функцию, возвращающую тот самый маркер. В коде ниже функция принимает параметры: классы Us откуда я беру название и координаты и необязательный параметр gMarkerGoogleType – тип маркера.

private GMarkerGoogle GetMarker(Us us, GMarkerGoogleType gMarkerGoogleType= GMarkerGoogleType.red)
        {
            GMarkerGoogle mapMarker = new GMarkerGoogle(new GMap.NET.PointLatLng(us.сoordinates[0].lat, us.сoordinates[0].lon), gMarkerGoogleType);//широта, долгота, тип маркера
            mapMarker.ToolTip = new GMap.NET.WindowsForms.ToolTips.GMapRoundedToolTip(mapMarker);//всплывающее окно с инфой к маркеру
            mapMarker.ToolTipText = us.id; // текст внутри всплывающего окна
            mapMarker.ToolTipMode = MarkerTooltipMode.OnMouseOver; //отображение всплывающего окна (при наведении)
            return mapMarker;
        }

Необходимо сказать о GMapOverlay – это слой, похожий на слой в Photoshop, который можно описать как прозрачный холст размером с карту на который мы размещаем все маркеры.

Теперь функция возвращающая слой с маркерами. Входные параметры: список классов с именами и координатами, название слоя и все тот же необязательный параметр gMarkerGoogleType.

private GMapOverlay GetOverlayMarkers(List<Us> uss, string name, GMarkerGoogleType gMarkerGoogleType= GMarkerGoogleType.red)
        {
            GMapOverlay gMapMarkers = new GMapOverlay(name);// создание именованного слоя 
            foreach (Us us in uss)
            {
                gMapMarkers.Markers.Add(GetMarker(us, gMarkerGoogleType));// добавление маркеров на слой
            }
            return gMapMarkers;
        }

Добавление слоя. Вызываем функцию создающую слой и добавляем его к слоям контрола карты:

gMapControl1.Overlays.Add(GetOverlayMarkers(groups, "GroupsMarkers"));

то же самое, но с указанием типов маркеров:

gMapControl1.Overlays.Add(GetOverlayMarkers(LUs, " GroupsMarkers", GMarkerGoogleType.blue));

Можно отображать / скрывать слои

gMapControl1.Overlays[0].IsVisibile = false;

Слой добавлен, теперь просто обновляем контрол

gMapControl1.Update();// обновить контрол

На выходе получаем карту с нанесенными на нее маркерами

Тепловая карта

Изначально хотелось сделать тепловую карту по количеству маркеров в районах разных городов, но для этого придется искать и добывать координаты границ этих районов. А это больше труда и времени, а дедлайн, как всегда, уже позавчера, поэтому было придумано составлять карту по концентрациям в тайлах. Чтобы это хорошо смотрелось, тайлы лучше брать меньшего размера, чем для нынешнего зума. И из таких соображений я делаю набор слоев для каждого зума, и при изменении зума меняю слой.

Код создания слоев с цветными полигонами:

public void GetHeatMap(List<Us> LUs, string name, bool AllCoord = true)
        {
            MercatorProjection mercatorProjection = new MercatorProjection(); // класс с функциями https://github.com/radioman/greatmaps/blob/master/GMap.NET.Core/GMap.NET.Projections/MercatorProjection.cs#L73
            for (int myZoom = gMapControl1.MinZoom + 3; myZoom <= gMapControl1.MaxZoom + 3; myZoom++)
            {
                GMapOverlay gMapOverlay = new GMapOverlay(name + myZoom); // создание слоя с именем включающий зум
                int tileSizePx = (int)mercatorProjection.TileSize.Height; //размер тайла
                List<GPoint> gPoints = new List<GPoint>();//список точек 
                foreach (Us us in LUs)
                {
                    if (AllCoord)
                    {
                        foreach (Coordinat coordinat in us.сoordinates)
                        {
                            GPoint pixelCoord = mercatorProjection.FromLatLngToPixel(coordinat.lat, coordinat.lon, myZoom);
                            gPoints.Add(new GPoint(pixelCoord.X / tileSizePx * tileSizePx, pixelCoord.Y / tileSizePx * tileSizePx));
                        }
                    }
                    else
                    {
                        GPoint pixelCoord = mercatorProjection.FromLatLngToPixel(us.сoordinates[0].lat, us.сoordinates[0].lon, myZoom);//из широты долготы в пиксели
                        gPoints.Add(new GPoint(pixelCoord.X / tileSizePx * tileSizePx, pixelCoord.Y / tileSizePx * tileSizePx));
                    }
                }
                var group = gPoints.GroupBy(r => r).ToList();
                int TileMaxCount = group.Max(r => r.Count());// максимальное содержание маркеров на тайле
                foreach (var item in group)
                {
                    int minus = Convert.ToInt32(155 * item.Count() / TileMaxCount);//сколько вычитать из РГБ
                    if (minus == 0) { minus++; }
                    Color color = Color.FromArgb(200, 255, 155 - minus, 155 - minus);// задаем цвет для использования
                    List<PointLatLng> pointLatLngs = new List<PointLatLng>()
                {
                    mercatorProjection.FromPixelToLatLng(item.Key, myZoom),
                    mercatorProjection.FromPixelToLatLng(item.Key.X+tileSizePx, item.Key.Y, myZoom),
                    mercatorProjection.FromPixelToLatLng(item.Key.X+tileSizePx, item.Key.Y+tileSizePx, myZoom),
                    mercatorProjection.FromPixelToLatLng(item.Key.X, item.Key.Y+tileSizePx, myZoom)
                }; //создаем список точек для полигона
                    GMapPolygon gMapPolygon = new GMapPolygon(pointLatLngs, item.Key.ToString()); //создаем полигон из списка точек
                    gMapPolygon.Fill = new SolidBrush(color);// заливка
                    gMapPolygon.Stroke = new Pen(color, -1); // рамка
                    gMapPolygon.IsVisible = true; // видимость 
                    gMapOverlay.Polygons.Add(gMapPolygon); // добавляем полигон
                }
                gMapControl1.Overlays.Add(gMapOverlay);// добавляем слой
                GC.Collect();// сбор мусора
            }
        }

Код при изменении зума:

private void gMapControl1_OnMapZoomChanged()
        {
            if (AllPolygonsToolStripMenuItem.CheckState == CheckState.Checked)
            {
                CheckHeatMap("AllPolygons");
                gMapControl1.Update();
            }
        }

Код функции:

private void CheckHeatMap(string name)
        {
            foreach (GMapOverlay gMapOverlay in gMapControl1.Overlays.Where(r => r.Id.Contains("Polygons")))
            {
                gMapOverlay.IsVisibile = false;
            }// все слои содержащие слово полигон невидимы
            if (name != null)
            {
                name += gMapControl1.Zoom + 3;
                GMapOverlay gMapOverlaySearch = gMapControl1.Overlays.Where(r => r.Id == name).FirstOrDefault();
                gMapControl1.Overlays.Where(r => r.Id == name).FirstOrDefault().IsVisibile = true;//видимость слоя с заданным именем
            }
        }

При приближении размер тайлов меняется:

Заключение

В статье показано как начать знакомство с GMap: работать со слоями, тайлами, полигонами и добавлять маркеры. Надеюсь, идеи и код, изложенные в статье, будут полезны при решении ваших задач.

Также предлагаю заглянуть:

Gmap.Net Beginners Tutorial

OpenStreetMap в Visual Studio 2017. GMap.Net

Советуем почитать