Сотовый маляр

в 17:50, , рубрики: Алгоритмы, визуализация, гексагональная сетка, информационная безопасность, метки: ,

Известно, что набором правильных шестиугольников можно описать практически любой контур и создать тем самым фигуру произвольной формы. Такой принцип можно применить при построении топографических карт на подобие тому, как это реализовано в Sid Meier’s Civilization V, или же использовать в системах, основой которых являются те самые шестиугольники (сотовые операторы). Для придания информативности, каждой ячейке карты можно присваивать контрольное значение, будь то уровень сигнала сети, высота, глубина, наличие определенного вида ресурсов и т.п., характеризующее участок карты — соты. Это может выглядеть примерно так. Однако для глаз данное представление является не вполне презентабельным, и его можно визуализировать, добавив каждой ячейке соответствующий цвет.

Конструкторские работы

Для начала нужно построить каркас карты – ячеистую структуру. Есть некоторые решения данной задачи, например на codeproject, но конечная цель там немного отличается. Итак построим сетку в горизонтальной проекции, суть заключается в вычислении новой координаты согласно математическому управлению, при этом на каждый нечетный ряд происходит сдвиг на постоянную величину, зависящую от длины стороны шестиугольника:

private void DrawHexangleHorizon(Graphics gr, int i, int j)
        {
            int shft;
            int shft2;
            GraphicsPath gp;
            if (i % 2 != 0)
            {
                shft = (int)(Side * Math.Sqrt(3) / 2);
                shft2 = (int)((i - 1) * 1.5 * Side);
                gp = new GraphicsPath(new PointF[7] 
            { 
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3)), 20+shft2),                                                 //init 1-1
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3))+(int)(Side * Math.Sqrt(3)/2),20+shft2+Side/2),                                            //1->2
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3))+(int)(Side * Math.Sqrt(3)/2), 20+shft2 +Side+ Side/2),    //2->3
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3)), 20+shft2 + 2*Side),               //3->4
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3))-(int)(Side * Math.Sqrt(3)/2), 20+shft2 +Side+ Side/2),                    //4->5
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3))-(int)(Side * Math.Sqrt(3)/2), 20+shft2 + Side/2),         //5->6
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3)), 20+shft2)                                                  //finish 6->1
            },
                new byte[7]
            {
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
            }
                );
            }
            else
            {
                shft2 = (int)((i - 1) * 1.5 * Side);
                shft = 0;
                gp = new GraphicsPath(new PointF[7] 
            { 
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3)), 20+shft2),                                                 //init 1-1
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3))+(int)(Side * Math.Sqrt(3)/2),20+shft2+Side/2),                                            //1->2
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3))+(int)(Side * Math.Sqrt(3)/2), 20+shft2 +Side+ Side/2),    //2->3
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3)), 20+shft2 + 2*Side),               //3->4
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3))-(int)(Side * Math.Sqrt(3)/2), 20+shft2 +Side+ Side/2),                    //4->5
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3))-(int)(Side * Math.Sqrt(3)/2), 20+shft2 + Side/2),         //5->6
                new Point(20+shft+j*(int)(Side * Math.Sqrt(3)), 20+shft2)                                                  //finish 6->1
            },
                new byte[7]
            {
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
                (byte)PathPointType.Line,
            }
                );
            }
            gr.DrawPath(Pens.Black, gp);
            gr.FillPath(Sbr, gp);
        }

После выполнения получится что-то такое:
image
Если данный каркас наложить на реальный плоский объект и «вычесть», останутся только те ячейки, которые и являются границами объекта.

Малярные работы

Когда готов аппарат строительства каркаса, т.е. границ карты и ячеек, можно их и покрасить в соответствующий цвет. Правила для окрашивания можно придумать сколь угодно разные. В моем случае, ячейка с большим значением окрашивалась в красный цвет, с меньшим в синий колер. Для решения задачи визуализации применим линейный градиент стандарта RGB. Разукрашиваем:

private void ColoredGrig(Graphics gr, double[,] mas)
        {
            double max = double.MinValue;
            double min = double.MaxValue;

            for (int i = 0; i < 10; i++)
            {
                for (int j = 0; j < 10; j++)
                {
                    if (mas[i, j] > max)
                    {
                        max = mas[i, j];
                    }
                    if (mas[i, j] < min)
                    {
                        min = mas[i, j];
                    }
                }
            }
            double average = (max + min) / 2.0;

            for (int i = 0; i < 10; i++)
            {
                for (int j = 0; j < 10; j++)
                {
                    var c = new Color();
                    var red = new double();
                    var green = new double();
                    var blue = new double();
                    if (mas[i, j] == max)
                    {
                        red = 255;
                        blue = 0;
                        green = 0;
                    }
                    if (mas[i, j] == min)
                    {
                        red = 0;
                        blue = 255;
                        green = 0;
                    }
                    if (mas[i, j] == average)
                    {
                        red = 0;
                        blue = 0;
                        green = 255;
                    }
                    //warm colors
                    if ((mas[i, j] < max) && (mas[i, j] >= ((average + max) / 2)))
                    {
                        red = 255;
                        green = 255 - ((mas[i, j] - ((average + max) / 2)) * 255) / (max - ((max + average) / 2));
                    }
                    if ((mas[i, j] > average) && (mas[i, j] < ((average + max) / 2)))
                    {
                        red = ((mas[i, j] - average) * 255) / (((max + average) / 2) - average);
                        green = 255;
                    }
                    //cold colors
                    if ((mas[i, j] < average) && (mas[i, j] > ((average + min) / 2)))
                    {
                        blue = 255 - ((mas[i, j] - ((average + min) / 2)) * 255) / (average - ((average + min) / 2));
                        green = 255;
                    }
                    if ((mas[i, j] > min) && (mas[i, j] <= ((average + min) / 2)))
                    {
                        blue = 255;
                        green = ((mas[i, j] - min) * 255) / (((average + min) / 2) - min);
                    }
                    c = Color.FromArgb((int)red, (int)green, (int)blue);
                    Sbr = new SolidBrush(c);
                    DrawHexangleHorizon(gr, i, j);                   
                }
            }
        }

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

Другая сторона

И еще. Полученные функции можно применять при использовании карт самоорганизации Кохонена для анализа многомерных данных и кластеризации признаков в сферах, для которых необходимо анализировать большие потоки связанной информации, например для анализа сетевого трафика с целью выявления закономерностей, отклонений или аномалий, как в магистральных каналах связи, так и в локальных сетях. Данный вид нейронных сетей основан на конкурентном обучении, при организации которого применяются латеральные связи между нейронами. При обучении сети используется метод обучения без учителя, то есть результат обучения зависит только от структуры входных данных. Такое направление анализа не является сигнатурным, что в теории может создать новую ветвь продуктов по сетевой безопасности.

Автор: kimssster

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js