Разработка элемента управления для отображения векторных карт

Введение



Сейчас существует огромное количество форматов для хранения изображений. Все они представляют собой решения для использования в конкретных задачах и целях. Все форматы для хранения изображений делятся на два больших класса: растровые изображения (растровая графика) и векторные изображения (векторная графика). У каждого из классов есть свои достоинства и недостатки. Однако следует отметить что векторные изображения являются отличным средством визуализации информации и удобны в использовании. В частности можно выделить векторные карты которые всё большую популярность. И действительно они оказываются просто не заменимы в случаях когда необходимо представить информацию об территориях объединенные по определённому признаку. Например страны, области распространения каких то видов живых существ. В этом случае можно использовать графическую раскраску регионов для представления имеющейся информации деля имеющиеся данные на уровни и сопоставляя каждому уровню цвет. Примером такой информации может быть плотность населения на данной области, или др. Именно заинтересованность в этой теме и подтолкнуло к идее создания элемента управления для отображения векторных карт с возможностью отображения информации.




1. Разработка и анализ технического задания



1.1 Цель и назначение разработки



Целью разработки является создание элемента управления для отображения карт, представляющих собой векторные изображений в формате EPS (Encapsulated PostScript), удобного и простого в использовании. Работа векторными картами предоставляет возможность визуального представления информации связанной с геополитикой, геологическими исследованиями. 



1.2 Основание разработки



Основанием для разработки данного элемента управления послужила тема выбранная мной в качестве выпускной квалификационной работой бакалавра. Данная тема была выбрана исходя из личного интереса к данной тематике, а также реального применения результатов моей работы в процессах разработки программного обеспечения.



1.3 Область применения



Данный комплекс элементов управления может использоваться при разработке программного обеспечения для работы с векторными картами в формате EPS (Encapsulated PostScript).



1.4 Требования предъявляемые к проекту



-элемент управления должен обеспечивать необходимую функциональность для работы с векторными картами.

- простой и понятный интерфейс для построения приложений использующих этот элемент.

- использование современных технологий разработки программного обеспечения.

- расширяемость, возможность усовершенствования, на ряду с основным назначением.

- выводом векторных карт в формате EPS (Encapsulated PostScript), необходимо предусмотреть вывод дополнительной информации.

Элемент управления должен предоставлять следующие основные функции:

-	масштабирование

-	автомасштабирование в пределах видимой области

-	расположение изображения в пределах видимой области

-	перемещение изображения

-	выделение замкнутых областей по щелчку мыши 

-	раскрашивание выделенной замкнутой области

-	автораскрашивание

-	раскрашивание с учётом дополнительной информации



1.5 Требования к программному и техническому обеспечению



Операционная система Windows XP и выше.

Microsoft.NET Framework SDK v1.1или выше

Microsoft Visual Studio.NET 2003 или выше

Pentium 4 1800 Мгц, 512 оперативной памяти, 2Мб на жёстком диске.



1.6 Анализ технического задания



Исходя из требований проекта и учитывая изученные на данный момент языки программирования выбор делался из следующего перечня языков: Visual Basic, Borland Delphi, Borland C++ Builder, Visual C++, Visual Studio.Net 2003 Visual C#. Язык Visual Basic очень прост в освоении но его возможности ограничены он скорее подходит для быстрого создания красивых приложений, и приложений использующих пакет Microsoft Office. Данный язык не подходит по функциональности и сложен для реализации предъявляемых требований. Borland C++ Builder представляет классический язык С++ с набором библиотек. Основным недостатком является моральное устаревание и отсутствие поддержки современных технологий. Язык С++ прекрасно переноситься на другие платформы в частности Linux но так как в Borland C++ Builder присутствуют собственные библиотеки не реализованные в других платформах что не возможно осуществить переноску даже с открытым кодом, что ставит под сомнение коммерческое использование компонента. Visual C++ тоже отличается наличием собственных библиотек что более сильно связывает его с ОС Windows. Конечно в последних версия есть поддержка Framework обозначаемая как ‘управляемый С’, но зачем использовать что-то подогнанное. По этому этот язык не подходит нам. Borland Delphi очень хороший язык. Функционален, имеются достаточные инструменты для реализации нашего проекта, в последних версия имеется поддержка технологии.Net разработанной компанией Microsoft что повышает переносимость программного обеспечения на другие платформы, конечно с реализованной технологией.Net на этой платформе. Если сравнить его с Visual C#. То видны неоспоримые преимущества последнего. Также идёт соперничество между двумя с моей точки зрения наиболее перспективными языками С# и Java. С# - это язык программирования, синтаксис которого очень похож на синтаксис Java (но не идентичен ему). Например, в С# (как в Java) определение класса состоит из одного файла (*.cs), в отличие от C++, где определение класса разбито на заголовок (*.h) и реализацию (*.срр). Однако называть С# клоном Java было бы неверно. Как С#, так и Java основаны на синтаксических конструкциях C++. Если Java во многих отношениях можно назвать очищенной версией C++, то С# можно охарактеризовать как очищенную версию Java. Синтаксические конструкции С# унаследованы не только от C++, но и от Visual Basic. Например, в С#, как и в Visual Basic, используются свойства класс сов. Как C++', С# позволяет производить перегрузку операторов для созданных вами типов (Java не поддерживает ни ту, ни другую возможность). С# — это фактически гибрид разных языков. При этом С# синтаксически не менее (если не более) чист, чем Java, так же прост, как Visual Basic, и обладает практически той же мощью и гибкостью, что и C++. Подводя итоги, еще раз выделим основные особенности С#.

- Указатели больше не нужны! В программах на С#, как правило, нет необходимости в работе с ними (однако если вам это потребуется, пожалуйста, возможности для работы с указателями в вашем распоряжении).

- Управление памятью производится автоматически.

- В С# предусмотрены встроенные синтаксические конструкции для работы с перечислениями, структурами и свойствами классов.

- В С# осталась возможность перегружать операторы, унаследованные от C++. При этом значительная часть возникавших при этом сложностей ликвидирована.

- Предусмотрена полная поддержка использования программных интерфейсов. Однако в отличие от классического СОМ применение интерфейсов это не единственный способ работы с типами, используя различные двоичные модули..NET позволяет передавать объекты (как ссылки или как значения) через границы программных модулей.

- Также предусмотрена полная поддержка аспектно-ориентированных программных технологий (таких как атрибуты). Это позволяет присваивать типам характеристики (что во многом напоминает COM IDL) для описания в будущем поведения данной сущности. Возможно, самое важное, что необходимо сказать про язык С#, — это то, что он генерирует код, предназначенный для выполнения только в среде выполнения. NET. Например, вы не сможете использовать С# для создания классического СОМ-сервера. Согласно терминологии Microsoft код, предназначенный для работы в среде выполнения.NET, — это управляемый код (managed code).


2. Разработка архитектуры элемента управления



При разработке архитектуры было принято решение отдельно создать элемент управления который будет служить только для отображения векторных карт и хранения данных(цвет, набор координат замкнутых областей). Дополнительные элементы управления буду выдавать лишь набор цветов(выбранных по определённому алгоритму) для раскрашивания.



2.1 Основной элемент управления



При разработке элемента управления мы будем руководствоваться принципами Объектно-Ориентированного Программирования. Основная парадигма которого гласит: “Реши какие потребуются классы обеспечь полный набор операций ля каждого класса явно вырази общность через наследование”. В результате проведения объектной декомпозиции были выделены следующие классы: 

Класс «элемент управления» CntrIm который наследуется от класса UserControl который предоставляет возможности создания элементов управления. Класс CntrIm выступует как класс в котором будет осуществляться отрисовка векторных изображений и обеспечиваться доступ к хранимой информации о изображении и вложенных объектах, осуществляется получение координат замкнутых областей из файла и передачу их в экземпляр класс MyMap для объединения в замкнутые области и хранения. 

Класс MyMap. Для класса CntrIm является вложенным и будет являться своего рода диспетчером управляющим картой. Будет управлять вложенными объектами, осуществлять передачу необходимых данных в обоих направлениях, отвечать за систему координат, масштабирование изображения, переключение между выделенными областями, предоставление координат для отрисовки объектов на элементе управления. В нём будет хранится коллекция объектов обозначающих замкнутые области из которых составлена карта.

Класс PathD. Смысловая нагрузка этого класса это замкнутые области на векторных картах, которыми могут быть страны, регионы и д.р. Данный класс хранит коллекцию экземпляров класса PointD. Класс PointD является вложенным для класса PathD. Данный класс также хранит описание в какой цвет будет раскрашена данная замкнутая область. Он будет обеспечивать доступ к коллекции хранимых точек из которых состоят замкнутые области.

Класс PointD. Смысловая нагрузка этого класса это точка из которых создаются замкнутые области. Этот класс предназначен лишь для хранения координат полученных из файла. Иерархия представлена на Рис. 2.1





CntrIm

MyMap

1

1

1

PathD

∞

1

PointD

∞

Рис. 2.1 Иерархия классов



2.2 Дополнительные элементы управления



Класс AutoColor. Взаимодействие этого класса с элементом управления представляет собой контрактную модель. Экземпляр класса AutoColor получает коллекцию областей для анализа (массив экземпляров класса PathD), анализирует данные и выдаёт массив экземпляров класса Color, то есть цвета в которые будут раскрашены области с учётом того что рядом расположенные области не будут одинаково раскрашены. Выбор цветов производится из условия оптимального раскрашивания. Массив экземпляров класса PathD хранится в экземпляре класса MyMap. Схема взаимодействия приведена на Рис. 2.2.1

Класс UserControl1. Является элементом управления унаследованным от класса UserControl. И создан для выдачи экземпляра класса Color то есть цвета в который будет окрашена выделенная в элементе управления CntrIm замкнутая область. Схема взаимодействия приседена на рисунку 2.2.2





AutoColor

 - [] Color msClr

+[]Color GetMsClr

 +AutoColor([]PointD)

- GetMs

- Run()

CntrIm

- MyMap mapD

+ MyMap GetMap

mapD : MyMap

- [] PathD msPth

+ [] PathD AllPath

[] PathD

msPth[i] : PathD

- Color clr

+ Color ColorP

i++

i<length

Рис. 2.2.1 Схема взаимодействия c классом AutoColor



Класс SetDiapColor. Является элементом управления унаследованным от UserControl и предназначен для выдачи схемы раскраски замкнутых областей. Он получает количество замкнутых областей. В этом элементе управления каждой области присваивается число и задаются диапазоны для раскрашивания. В зависимости от этих параметров выдаётся массив типа Color. То есть номер экземпляра в массиве соответствует в какой цвет будет раскрашена замкнутая область.





UserControl1

- Color clr

+ Color ClrSG



CntrIm

- MyMap mapD

+ MyMap GetMap

mapD : MyMap

- [] PathD msPth

+ [] PathD AllPath

msPth[] : PathD

- Color clr

+ Color ColorP

ColorDlg

+ Color Color

Рис 2.2.2 Схема взаимодействия с классом UserControl1





SetDiapColor

- length

+ Color [] GetMsClr

+ SetQuant( int )



CntrIm

- MyMap mapD

+ MyMap GetMap

mapD : MyMap

- [] PathD msPth

- int length

+ [] PathD AllPath

+ int Length

msPth[] : PathD

- Color clr

+ Color ColorP

i++

i<length

Ввод данных

Рис 2.2.3 Схема взаимодействия с классом SetDiapColor.


3. Разработка кода элемента управления



3.1 Класс PointD



Предназначен исключительно для хранения координат точек x и y и организации доступа к ним. Тип переменных в которых хранятся координаты определялся исходя из того что необходимо хранить числа с не менее чем 5ью знаками поле запятой.

Структура класса PointD:



public class PointD

{

private double x;		//координата x

private double y;		//координата y

public PointD()		//конструктор по умолчанию

{	…

}

public PointD(double x,double y) 

{…

}

public PuintD(PointD pd)	//конструктор копирования

{…

}

public double X		// свойство для доступа к координате X

{…

}

public double Y		//свойство для доступа к координате Y

{…

}


3.2 Класс PathD



Класс PathD несёт смысловую нагрузку и представляет собой замкнутую область. В его структуру включен массив типа PointD в который добавляются точки которые формируют замкнутую область. Включены поля хранящие количество точек в замкнутой области, координаты центра замкнутой области, цвет замкнутой области. Центр области вычисляется по формуле Xc=(x1+x2+…+xn)/n, Yc=(y1+y2+…+yn)/n, где Xc,Yc-координаты центра фигуры, n-количество точек замкнутой фигуры, x1..xn координаты по оси X точек 1..n, y1..yn координаты по оси Y точек 1..n. Добавление точек осуществляется так: создается массив длинной n+1 экземпляры класса PoitD копируются в новый массив, в последний элемент массива записывается новый экземпляр класса PoitD. Старый массив уничтожается «сборщиком мусора» элементом среды в которой идёт выполнение.

Структура класса PathD



public class PathD

{

private int length;		//количество точек в замкнутой области

private PointD[] msPtd;	//массив точек

private PointD centr;		//центр фигуры

private Color clr;		//цвет в который будет раскрашена область при //при отрисовке

public PathD()

{…

}

public void AddPoint(PointD pointD)	//метод добавляющий точку к 

//области

{	…

}

public int Length 

{ …

}

public PointD[] PathPoints	//свойство выдающее массив точек области

{…

}

public PointD Centr		//свойство выдающее центр области

{	…

}

public Color ColorP		//свойство для получения и установки цвета

{	…

}

}



3.3 Класс MyMap



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

Стрктура класса MyMap:



public class MyMap

{

private double scale;		//поле масштаба

private int length;		//поле количество замкнутых областей

private int space;		//отступ от границы элемента

private double kx;		//коэффициент по оси х

private double ky;		//коэффициент по оси y

private double dx;		//смещение оси х

private double dy;		//смещение оси y

private PathD [] msPthd;	// массив замкнутых областей

private Pen [] msPn;		//массив перьев для отрисовки обастей

// iой области соответствует iое перо

}



Метод public void AddPath(PathD pathD) предназначен для добавления замкнутой области к коллекции областей в качестве параметра ей передается объект типа (PathD

Свойство public PathD[] AllPaths выдаёт копию массива замкнутых областей

Свойство public Pen[] AllPen выдаёт копию массива перьев соответствующих каждой области.

Свойство public int Length выдаёт количество замкнутых областей 

Свойство public double Scale выдаёт или устанавливает масштаб изображения

Метод public void SetAxes(double kX,double kY) предназначен для установки коэффициентов по координатным осям.

Метод public void SetXYAxes(double dX, double dY) предназначен для установки смещения координатных осей относительно исходных.

Свойство public double KX	предназначен для установки и получения коэффициентов по оси X.

Свойство public double KY	предназначен для установки и получения коэффициентов по оси Y.

Свойство public double DX предназначен для установки и получения смещения координатной оси X относительно исходного расположения.

Свойство public double DY предназначен для установки и получения смещения координатной оси Y относительно исходного расположения.

Метод public void SetXYtoView() предназначен для нахождения смещения координатных осей для расположения изображения в видимой области в исходной системе координат. То есть находятся минимальные координаты по оси X и Y затем из них вычитается смещение от границы. Смещение от границы сделано исключительно в эстетических целях.

Метод public void SetScaleToView(int height,int width) предназначен для вычисления и установки масштаба для вписывания в окно заданных размеров height, width. Вычисление происходит методом нахождения минимальных и максимальных координат в имеющихся замкнутых областях. Затем происходит выбор наиболее протяженной разницы между максимальной и минимальной координатой по X и Y. Вычисление масштаба по формуле scale = (height-space*2)/h по оси Y и scale = (width-space*2)/l по оси X.

Метод public void Allocate(int nPth,int type) предназначен для установки пера замкнутой области с номером nPth. Переменная type предназначена для представления какой тип выделения замкнутой области. Выделение одиночной или группы областей. В данном случае реализовано только для одиночного выделения областей. Алгоритм представлен ниже на блок схеме Рис. 3.3. 

Смысл в том что если номер области совпадает с уже выделенной областью то ничего не происходит, если же область другая то все перья сбрасываются а в элемент массива с соответствующим индексом заносится новое перо(выделенное).






Конец

Начало

type =0(тип выделения)

нет

да

msPn[nPth]!=new Pen(Color.Black,4), nPth-ая область не выелена

нет

да

i<length

да

msPn[i]= null;i++

msPn[nPth] = new Pen(Color.Black,4);

нет

Рис. 3.3 блок-схема алгоритма метода public void Allocate(int nPth,int type)



Метод public void SetClrPth(int NPth, Color Clr) Предназначен для установки свойства замкнутой области с номером NPth в значение Clr.

Метод public int GetNamberPath (int crX, int crY) предназначен для определения какой замкнутой области принадлежности координаты точки. Если точка не принадлежит ни одной из областей то метод возвращает -1 в противном случае возвращает номер области.

Смысл определения принадлежности точки фигуре заключается в следующем:

Пусть у нас есть точка с координатами x,y: Принадлежность точки стороне проверяется просто: если концевые точки стороны A(x1,y1) и B(x2,y2), то, если точка Z(x,y) принадлежит стороне, то должно существовать такое число p, 0<=p<=1, что



x=p*x1+(1-p)*x2

y=p*y1+(1-p)*y2



(тут используется то, что если прямая проходит через A и B, то точки прямой имеют координаты (p*x1+(1-p)*x2, p*y1+(1-p)*y2), p действительное число. При 0<=p<=1 мы получаем отрезок между точками A и B).

Далее, проведем из точки Z прямую, параллельную оси OX (ее уравнение будет Y=y) и проверим, сколько сторон треугольника пересекает полулуч, идущий от точки Z, например, вправо. Если 0, то Z - вне треугольника, если 1 - то внутри, если 2 - то снаружи

Надо конкретно обработать случай, когда прямая пересекает одну из вершин.

Может быть 2 случая рассмотренных на Рис. 3.3.1.





Рис 3.3.1 2а случая пересечения лучом вершины


В случае 1, когда оба ребра, входящих в вершину A, лежат по одну сторону от прямой, количество пересечений можно считать равным двум (или нулю), в случае 2, когда ребра лежат по разные стороны от прямой, число пересечений примем равным 1. Если прямая проходит по стороне, то число пересечений будем считать равным 2. Алгоритм нахождения номера выделенной области рассмотрен на блок схеме Рис. 3.3.2.






double x,y; int pth=-1;

x=oldDx+Convert.ToDouble(crX)/(this.mapD.Scale*this.mapD.KX);

y=oldDy+Convert.ToDouble(crY)/(this.mapD.Scale*this.mapD.KY);

double x1,y1,x2,y2; double p; double nx; double ny=y; int per=0;



Начало

k<length

да

нет

per=-1;

j<msPth[k].Length-1

нет

да

x1 = mapD.AllPaths[k].PathPoints[j].X;

y1 = mapD.AllPaths[k].PathPoints[j].Y;

x2 = mapD.AllPaths[k].PathPoints[j+1].X;

y2 = mapD.AllPaths[k].PathPoints[j+1].Y;



y1!=y2

p=(ny-y2)/(y1-y2);

nx=p*x1+(1-p)*x2;



да

нет

A

B

C

D

E

F




(p>0)&&(p<1)&&(x<nx)

да

нет

A

per++

nx=x1 && ny=y1

да

per++

нет

(per%2)==1

нет

pth=k;



return pth



Конец

j++

k++

B

C

D

F

E

Рис. 3.3.2 Блок-схема алгоритма нахождения номера выделенной области


3.4 Класс CmpView 



Предназначен для отрисовки элементов векторной каты, открытия файла с векторным изображением и получения координат замкнутых областей. А также обеспечения взаимодействия с внешней средой.



public class CntrIm: System.Windows.Forms.UserControl

{

private string Path;		//поле для хранения пути к файлу

private MyMap mapD;	//поле для хранения объекта MyMap

private bool flag;		//нажата кнопки мыши или нет

private bool flRec;		//рисовать прямоугольник выделения

private int oldX;		//координаты нажатия кнопки мыши

private int oldY;

private int curX;		//текущее положение мыши

private int curY;

private double oldDx;		//старое смещение координатных осей

private double oldDy;

public delegate void MapLoadEndDelegate();	//определение делегата

public event MapLoadEndDelegate MapLoadEnd;	//определение события

}



Метод public void FileOpen(string Pth) предназначен для открывания файла векторного изображения и получении координат замкнутых областей. В начале создается новый экземпляр класса MyMap, устанавливается начальный масштаб и коэффициенты координатных осей. Затем идёт открытие потока для чтения где параметром вызова выступает строка с полным путем к файлу. 


StreamReader StrmR = new StreamReader(Path);



Чтение из потока происходит по строчно, строка записывается в переменную str которая и будет использоваться для анализа, выделения команд и координат замкнутых областей. При поступлении набора символов предшествующего массивам координат замкнутых областей флаг flag устанавливается в значение true что говорит о том что начинается считывание координат замкнутых областей. Далее считывается следующая строка и происходит выделение координат. Пока не достигнут конец строки или не найден один из командных символов происходит выделение координат. Из строки по символьно читаются цифры до появления пробела что говорит об окончании значения координаты. Ведётся подсчёт считанных чисел для определения вида координаты по оси X или по оси Y. Не чётные числа означают координаты по оси X чётные числа - координаты по оси Y. Затем создается экземпляр типа PointD с полученными координатами и происходит добавление в экземпляр класса PathD. То есть формируется замкнутая область. Процесс считывания и добавления координат, формирование замкнутая области происходит до того как встречается следующая командная строка. В этот момент сформированная замкнутая область добавляется в коллекцию областей, передаётся в экземпляр класса MyMap а флаг flag устанавливается в значение false сигнализируя о том что чтение координат и формирование области закончено. Далее процесс повторяется пока не будет достигнут конец файла. После чего поток закрывается, ресурсы высвобождаются. Далее вызываются методы SetScaleToView(this.Height,this.Width) и SetXYtoView() экземпляра класса MyMap для масштабирования и расположения изображения в пределах окна. После чего если определёно то вызывается функция сопоставленная событию окончания чтения файла. Алгоритм чтения координат из файла представлен на блок-схеме Рис 3.4.

Метод private void CntrIm_MouseDown object sender, System.Windows.Forms.MouseEventArgs e) срабатывает когда происходит нажатие кнопки мыши. При этом flag устанавливается в значение true что означает кнопка нажата, и запоминаются текущие координаты мыши и смещение координатных осей.

Метод private void CntrIm_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) срабатывает когда происходит событие отпускание кнопки мыши. При этом flag устанавливается в значение false.

Метод private void CntrIm_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) срабатывает когда происходит передвижение мыши и запоминаются текущие координаты мыши. Необходимые для отрисовки прямоугольника выделения.

Метод public void ScaleToPoint(int crX,int crY,double sclN) предназначен для изменения масштаба изображения на заданную величину sclN в точку int crX,int crY.

Метод public void MovePoint(int oldx,int oldy, int crx,int cry) предназначен для перемещения изображения из точки с координатами oldx, oldy, в точку с координатами crx, cry.

Метод public void ScaleToRctangle(int oldx,int oldy,int crX,int crY) предназначен для вычисления масштаба для увеличения выделенной области до размеров экрана. Размеры выделенной области определяются первой: oldx,int oldy, и последней точкой int crX,int crY






Начало

(str=StrmR.ReadLine())!=null

нет

да

str=="/$fm 0 def"

нет

да

flag

да

pthD = new PathD();flag=true;

str= StrmR.ReadLine();



нет

mapD = new MyMap();mapD.Scale=1;

mapD.SetAxes(1,-1);this.Path = Pth;

StreamReader StrmR = new StreamReader(Path);

bool flag = false;string str;

int dss=0;PathD pthD=null;



A

B

C

D






Конец

int i=0;string rtt=""; int nm=0;

double [] xy=new double [2];



i<str.Length&&str[i]!='C'&&str[i]!='m'&&str[i]!='L'

да

нет

A

str[i]==' '

нет

да

xy[nm]=Convert.ToDouble(rtt);

nm++; rtt="";

(str=="@c"||str=="S")&&flag==true

нет

да

mapD.AddPath(pthD);

flag=false;

str[i]=='.'

нет

да

rtt+=',';

rtt+=',';

i++;

B

C

D

mapD.SetScaleToView(this.Height,this.Width);

mapD.SetXYtoView();StrmR.Close();



Рис 3.4 Блок-схема чтения координат из файла


Метод public void VeiwInWindow() предназначен для показа изображения в окне элемента управления. В нем вызываются методы SetScaleToView(this.Height,this.Width) и SetXYtoView() экземпляра класса MyMap для масштабирования и расположения изображения в пределах окна.

Метод RealScale() предназначен для отображения изображения в натуральную величину. Реализовано через установку свойства экземпляра класса MyMap Scale=1 и вызова метода SetXYtoView().

Свойство public bool VeiwRect предназначено для установки значения поля flRec, разрешение или запрещение отрисовки прямоугольника выделения.

Метод public void Allocate(int nPth,int type) предназначен для выделения области указанной в значением параметра nPth, и тип выделения type путём вызова метода Allocate(int nPth,int type) экземпляра класса MyMap.

Метод public void SetClrPath (int NPth,Color Clr) предназначен для установки области с номером NPth цвета окраса Clr. Путём вызова метода SetClrPth(NPth,Clr) экземпляра класса MyMap.

Метод public Color GetClrPath(int NPth) предназначен для получения цвета окраса области с номером NPth.(пример: mapD.AllPaths[NPth].ColorP)

Метод private void CntrIm_SizeChanged(object sender, System.EventArgs e) срабатывает когда изменяются размеры элемента управления. В нем вызываются методы SetScaleToView(this.Height,this.Width) и SetXYtoView() экземпляра класса MyMap для масштабирования и расположения изображения в пределах окна.

Метод protected override void OnPaint(PaintEventArgs e) является перегруженный методом вызываемым при перерисовке элемента управления. Чтобы получить изображение нам необходимо нарисовать все замкнутые области, чтобы нарисовать замкнутую область нам необходимо для данной области простись по всем точкам соединяя каждую точку с последующей. Первый цикл осуществляет перемещение по набору областей а второй цикл осуществляет переход от точки к точке внутри области выбранной в предыдущем цикле. Через методы классов мы получаем доступ к координата конкретной точки.



tmP[j].X=Convert.ToInt32((mapD.AllPaths[k].PathPoints[j].X-

mapD.DX)*mapD.KX*mapD.Scale);

tmP[j].Y=Convert.ToInt32((mapD.AllPaths[k].PathPoints[j].Y-

mapD.DY)*mapD.KY*mapD.Scale);



Эти координаты конвертируются в текущую систему координат и конвертируются из типа double в тип int и заносятся в экземпляр типа Point который находится в массиве длинна которого равна количеству точек в текущей замкнутой области. Когда все координаты текущей замкнутой области перенесены через экземпляр класса PaintEventArgs осуществляется доступ к экземпляру класса Graphics с помощью которого и осуществляется рисование на элементе управления. Сначала заливается текущая облать выбранным стилем и цветом, затем осуществляется доступ к кисти которым должна быть отрисована данная область. Доступ осуществляется через свойство экземпляра класса MyMap AllPen с указанием номера области, то есть соответствующему ей перу.



SolidBrush dBr = new SolidBrush(mapD.AllPaths[k].ColorP);

e.Graphics.FillPolygon(dBr,tmP);	Pen tPen;

if(mapD.AllPen[k]==null)

{

tPen=new Pen(Color.Black,1);

}

else

{

tPen=mapD.AllPen[k];

}

e.Graphics.DrawLines(tPen,tmP);



Если кисть не определена то отрисовка ведётся пером по умолчанию. После чего процесс повторяется для других областей. После этого осуществляется вывод в центре области порядкового номера этой области. Процесс доступа к точке центра области хранимой в каждом экземпляре PathD производится через методы и свойства экземпляра класса MyMap. Печать строки осуществляется с помощью метода экземпляра класса Graphics DrawString. Затем с помощью метода экземпляра класса Graphics DrawLines выводятся границы элемента. После проверки соответствующих булевых переменных происходит вывод прямоугольника выделения.



if (flRec==true&&flag==true)

{

Pen myP= new Pen(Color.Blue,1);

e.Graphics.DrawLine(myP,oldX,oldY,oldX,curY);

e.Graphics.DrawLine(myP,oldX,oldY,curX,oldY);

e.Graphics.DrawLine(myP,curX,oldY,curX,curY);

e.Graphics.DrawLine(myP,oldX,curY,curX,curY);

}



3.5 Класс AutoColor



public class AutoColor

{

public PathD [] msPth;	//коллекция замкнутых областей

public Color [] msClr;	//массив цветов областей

public AutoColor() 		//конструктор по умолчанию

{	msPth=null;

msClr=null;

}

}



Конструктор public AutoColor(PathD [] Pth) параметром для которого является коллекция областей для которой будет производиться автоматическое раскрашивание. Раскрашивание будет производиться по принципу оптимального использования цветов. В начальный момент времени создается массив типа Color в который будут заноситься цвета для раскрашивания соответствующих областей. Далее вызывается функция private int[,] GetMsGr() которая предназначена для создания матрицы пограничных областей. Строки и столбы этой матрицы представляют собой замкнутые области а узлы матрицы границы между областями. 

Например у нас есть 4 ре области как показано на Рис 3.5.





Рис 3.5 Пример расположения областей.



Значит матрица пограничных областей будет выглядеть следующим образом



0 1 1 0

1 0 1 0

1 1 0 0

0 0 0 0


То есть если деве области имеют общую границу то в узле матрицы номер сроки и номер столбца которого соответствует граничащим областям будет находится 1, если же общей границы нет то в узе остаётся 0. Сам алгоритм проверки на существование общей границы таков: Первый цикл перебирает области которые нужно сравнить 2цикл, вложенный в него перебирает координаты которые сравнивает и запоминает их в переменный которые будут использоваться при сравнении. 3ий цикл вложенный во второй перебирает области с которыми надо сравнить исключая текущую область. 4ый вложенный цикл в 3ий перебирает сами координаты. Если координаты текущей точки текущей области совпадают со сравниваемыми координатами данной области то в узлах матрицы с соответствующими номерами столбцов и срок записывается 1. И так процесс повторяется для всех областей. Функция возвращает заполненную матрицу. Далее представлена часть кода функции GetMsGr() заполняющая матрицу



for (int i=0;i<msPth.Length;i++) //1ывй цикл

{

for(int j=0;j<msPth[i].Length;j++) //2ой вложенный цикл

{

x1 = msPth[i].PathPoints[j].X; //запоминание координат точки для сравнения

y1 = msPth[i].PathPoints[j].Y;	

for (int k=0;k<msPth.Length;k++) //3ий вложенный цикл

{

for(int l=0;l<msPth[k].Length;l++) 4ый вложенный цикл

{

if(i!=k)

{

_x1 = msPth[k].PathPoints[l].X;

_y1 = msPth[k].PathPoints[l].Y;

if(x1==_x1&&y1==_y1) //сравнение координат текущей

{				//точки с запомненными

ms[i,k]=1;

}



Далее вызывается рекурсивная функция Run(i,ref mss); где параметрами являются i-номер области с которой надо начинать расчёт окраски, ref mss ссылка на массив пограничных объектов. Алгоритм работает следующим образом. В начале проверяется окрашен уже данная область или нет. Номер цвета содержится в диагонали матрицы. Если не окрашена область, то ищется цвет не принадлежащий окружающим эту область областям. Тесть выбирается jый цвет затем проходя по сроке ищет области которыми граничит и найдя такую область сравнивает текущий цвет с цветом этой области если совпадают то flag устанавливается из true в false а поиск продолжается по остальным областям. Если же нет то переход к следующей пограничной области. Если для данного j по всем граничащим областям не нашлось равного то данное число записывается как цвет данной области. Затем для всех областей в которые ёще не переходили и которые граничат с данной областью запускается рекурсивная функция Run(i,ref ms); а в узлы где номер столбца это область текущая а строка это область куда перейдём и наоборот записывается 2. Рекурсия повторяется. Пример массива на разных этапах при распределении цветов: 	



0 1 1 0	1 2 1 0	1 2 1 0	1 2 2 0	1 2 2 0

1 0 1 0	2 0 1 0	2 2 2 0	2 2 2 0	2 2 2 0

1 1 0 0	1 1 0 0	1 2 0 0	2 2 3 0	2 2 3 0

0 0 0 0	0 0 0 0	0 0 0 0	0 0 0 0	0 0 0 1



Затем в массив длинна которого равна количеству областей записываются соответствующие областям цвета. Массив записывается в поле msClr доступ к которому можно получить через свойство public Color [] GetMsClr. Алгоритм работы функции Run приведён в блок схеме Рис 3.5.1





Начало

ms[curPth,curPth]==0

нет

да

curPth!=i&&ms[curPth,i]>0&& ms[i,i]==j

нет

да

int j=1;	bool flag = false;

j<256&&!flag

нет

flag = true;

i<ms.Length

да

да

flag = false;

нет

A

C

D

B

E

F




Конец

ms[curPth,curPth]=j-1;

нет

i<ms.Length

да

ms[curPth,i]==1&&curPth!=i

ms[curPth,i]=2;

ms[i,curPth]=2;

Run(i,ref ms);

нет

i++

j++

A

C

D

B

E

F

Рис. 3.5.1 Блок-схема рекурсивного алгоритма распределения цветов.



3.6 Класс UserControl1



Как элемент управления предназначен для отображения цвета выбранного при вызове ColorDialog и хранения этого цвета с возможностью считывания этого цвета через свойство ClrSG(т.е. есть возможность последующего использования для окрашивания выделенной области в текущий цвет свойства ClrSG). А также для отображения цвета установленного из вне через это же свойство. Примечание: В данной работе применяется для отображения цвета выделенной области.



public class UserControl1: System.Windows.Forms.UserControl

{

private System.Windows.Forms.GroupBox groupBox1;

private System.Windows.Forms.Button button1;

private System.Windows.Forms.Label label1;

private int nomber;		//число, отображается в label1

public UserControl1()

{

}

protected override void Dispose(bool disposing)

{ …

}

}



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



public delegate void ColorSetDelegate(); 

public event ColorSetDelegate ColorSet;



Метод private void button1_Click(object sender, System.EventArgs e) срабатывает когда нажата кнопка button1. Создаётся экземпляр класса ColorDialog окна для выбора цвета если результат закрытия окна “OK” то цвет кнопки button1 меняется на выбранный. Свойство кнопки button1 BackColor используется и для хранения текущего цвета. Происходит наступление события ColorSet.



ColorDialog ClrDlg = new ColorDialog();

if(ClrDlg.ShowDialog()==DialogResult.OK)

{

button1.BackColor=ClrDlg.Color;

if(ColorSet!=null)

ColorSet();

}



Свойство public Color ClrSG предназначено для считывания и установки текущего цвета. 

Свойство public int Nomber предназначено для считывания и установки числа. Данное число отображается в объекте label1. в программе примере используется для отображения номера выделенной области.



3.7 Класс SetDiapColor



Предназначен для создания массива объектов типа Color (т.е. цветов). В начале через метод SetQuant(int Quant) мы устанавливаем в экземпляр типа SetDiapColor какое количество областей мы хотим раскрасить. Затем сопоставляя некоторые числа каждой области(эти числа могут означать что угодно например количество проживающих на данной области). Затем мы вводим диапазоны и сопоставляем каждому диапазону цвет в который будет окрашена область если число попадёт в заданный интервал. Когда закончен ввод. Нажимаем кнопку и начинается анализ. Каждое число сопоставленное области проверяется в какой интервал оно входит и в соответствии с этим цвет записывается в массив индекс ячейки в которую произведут запись цвета равен номеру рассматриваемой на данный момент области. Так происходит со всеми областями. Массив доступен через свойство GetMsClr. Затем перебирая ячейки массива передаём их в класс CntrIm через метод SetClrPath(i,msc[i]); где это номер области а msc[i] это элемент цвет в полученном массиве цветов.



public class SetDiapColor: System.Windows.Forms.UserControl

{

private System.Windows.Forms.Button button4;

private System.Windows.Forms.GroupBox groupBox2;

private System.Windows.Forms.Button button2;

private System.Windows.Forms.Button button1;

private System.Windows.Forms.ComboBox comboBox2;

private System.Windows.Forms.Button button3;

private System.Windows.Forms.GroupBox groupBox1;

private System.Windows.Forms.TextBox textBox1;

private System.Windows.Forms.ComboBox comboBox1;

private int QuantPaths;		//количество областей

private double [] msCh;		//сопоставленные каждой области число

private Color [] msChClr;		// сопоставленные каждой области цвет 

private double [] msDiap;		//массив где i-ый член хранит верхнюю 

//границу диапазона а i-1 нижнюю 

private Color[] msClr;		// сопоставленные диапазонам цвета

private int length;			//длина массива диапазонов

private Color DefCol;			// цвет по умолчанию

public SetDiapColor()		//конструктор поумолчанию

{…

}


Определение делегата и события на основе делегата. Событие наступает по нажатии кнопки button4 “раскрасить”



public delegate void ColorRsDelegate();

public event ColorRsDelegate ColorRs;



Метод public void SetQuant(int Quant) предназначен для создании массива чисел сопоставляемых областям количество которых передано в параметре Quant. Инициализации переменных значениями.

Метод private void textBox1_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) вызывается когда фокус ввода находится на элементе управления textBox1 и осуществляется нажатие кнопки. Если нажатая кнопка не цифра и не “,” которую можно вводить не более 1 раза то textBox1 блокируется для ввода

Если же нажата клавиша перевода каретки то происходит конвертация значения в тип Double и запись этого значения в массив чисел сопоставленных областям. Номер области определяется по записи выделенной в ComboBox1. 

Метод private void comboBox1_SelectedIndexChanged(object sender, System.EventArgs e) вызывается когда меняется выделенный индекс элемента управления comboBox1 и высвечивает в textBox1 число приписанное к данной области.	

Метод private void comboBox2_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) вызывается когда фокус ввода находится на элементе управления textBox1 и осуществляется нажатие кнопки. Если нажатая кнопка не цифра и не “,” которую можно вводить не более 1 раза то comboBox2 блокируется для ввода.

Метод private void button1_Click(object sender, System.EventArgs e) срабатывает когда происходит нажатие кнопки button1. Вызывается окно выбора цвета. И установка полученного цвета в соответствующую ячейку массива.

Метод private void button3_Click(object sender, System.EventArgs e) предназначен для добавления в массив диапазонов новый диапазон и также добавляется новый цвет для данного массива. При этом происходит сортировка для последующего определения принадлежности числа массиву.

Метод private void comboBox2_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e) срабатывает когда отрисовыватеся каждая запись выпадающего списка comboBox. Тесть В начале мы выводим цвет соответствующий данному индексу а затем верхнюю границу.



{

if(e.Index!=-1)

{

if(e.Index<length)

{

e.Graphics.FillRectangle(new SolidBrush(msClr[e.Index]), new Rectangle(e.Bounds.X+1, e.Bounds.Y+1, 13, 13));

e.Graphics.DrawString(msDiap[e.Index].ToString(),new Font("Arial",10),new SolidBrush(Color.Black),e.Bounds.X+14, e.Bounds.Y);

}

else

{

e.Graphics.FillRectangle(new SolidBrush(this.DefCol), new Rectangle(e.Bounds.X+1, e.Bounds.Y+1, 13, 13));

e.Graphics.DrawString("Default",new Font("Arial",10),new SolidBrush(Color.Black),e.Bounds.X+14, e.Bounds.Y);

}


Метод private void button2_Click(object sender, System.EventArgs e) срабатывает когда происходит нажатие кнопки button2 при этом происходит удаление по выделенному в comboBox2 индексу диапазона.

Свойство public Color [] GetMsClr предназначено для получения массива цветов в которые будет производиться раскрашивание.




4. Анализ полученных результатов



При разработки элемента управления мы использовали файлы Encapsulated PostScript созданные программой CorelDraw. На Рис. 4.1 и 4.2 приведены примеры того что выводил CorelDraw и изображение созданное моим компонентом.





Рис. 4.1 изображение в CorelDraw





Рис 4.2 изображение в моём компоненте



Оценивая выведенные изображения могу отметить что изображения очень похожи и контуры областей отчётливо видны. Но заметна и некая угловатость в вычерчивании областей в моём компоненте. Это объясняется тем что в CorelDraw используются линии Безье для которые представлены в исходном фале в формате 3х последовательных точек. В моём же компоненте это заменено на вычерчивание линий. Может быть в дальнейшем если копонент пригодиться то и это будет реализовано.


5. Руководство пользователя



Описание работы будет производиться на специально созданной демонстрационной программе.



5.1 Общи вид 



после запуска программа выглядит как показано на Рис. 5.1





Рис. 5.1 Общий вид



Созданный компонент находится в центре и обозначен синими прямыми углами.



5.2 Открытие файла





Рис 5.2 Открытие файла




Рис. 5.2.1 Отображение карты



5.3 Операции с изображением



5.3.1 Перемещение

Перемещение изображение осуществляется отмечание соответствующей функции обозначенную символом  наведение на карту указателя. Нажав на левую кнопку мыши перемещать изображение в нужном направлении.



5.3.2 Увеличение в точке

Увеличение изображение осуществляется отмечание соответствующей функции и установкой требуемого увеличения обозначенную символом  наведение на карту указателя и нажатие кнопки мыши.



5.3.3 Увеличение области

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





Рис 5.3.3 Прямоугольник увеличения



5.3.4 Вписывание изображения в область компонента

Для вписывания элемента в область компонента необходимо нажать кнопку обозначенную символом



5.3.5 Реальные размеры изображения

Для отображения элемента в реальных размерах нажать кнопку обозначенную символом





5.3.6 Выделение области на карте

Выделение области осуществляется отмечанием соответствующей функции обозначенной символом После чего поместите указатель на необходимую область и нажмите левую кнопку мыши. Область выделится. См Рис. 




5.4 Раскрашивание



Для примера раскрашивания областей было создано тир типа раскрашивания. Автоматическое, Ручное и Диапазонное то есть ни какие 2е граничащие области не будут окрашены в одинаковый цвет



5.4.1 Автоматическое

Автоматическое раскрашивание означает что все области будут раскрашены и ни какие 2е граничащие области не будут окрашены в одинаковый цвет. Это показано на Рис. 5.2.1



5.4.2 Ручное

При ручном раскрашивании выделите необходимую область. В области формы показанной на Рис. 5.4.2 отобразится текущий цвет области и её номер. Для изменения цвета выделенной области щёлкните мышью на раскрашенный прямоугольник. Появится окно выбора цвета. Выберете нужный вам цвет. На Рис. 5.4.2.1 показан конечный результат.





Рис. 5.4.2 Цвет области






Рис. 5.4.2.1 Ручное раскрашивание



5.4.3 Диапазонное

Для диапазонного раскрашивания нажмите кнопку “Таблиц”. Появится окно показное на Рис. 5.4.3.1 Заполните для каждой области число и заполните диапазоны и цвета для них. Затем нажмите кнопку “Раскрасить”. Результат представлен на Рис 5.4.3.3



		

Рис. 5.4.3.1				Рис. 5.4.3.2





Рис 5.4.3.3


Заключение



В рамках данного проекта, был разработан компонент который позволяет отображать векторные изображения формата Encapsulated PostScript созданные программой CorelDraw. Реализованы основные и широко используемые инструменты для работы с изображениями такие как масштабирование, автомасштабирование, расположение объекта в видимой области, перемещение объекта. Так же были реализованы возможности выделения областей расположенных на компоненте и различные алгоритмы раскраски которые были выделены в отдельные части проекта. С помощью них мощно представить различную информацию выведенную в виде раскрашенных групп областей. При создания компонентов были использованы одна из новейших технологий.Net разработанная компанией Microsoft, сам компонент разработан с моей точки зрения наиболее перспективном языке С# Полученные в результате работы компоненты пригодны для дальнейшего усовершенствования.

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




Список литературы



	Троелсен. Э. С# и платформа.NET. Библиотека программиста. — СПб.: Питер, 2004. —796 с.: ил.

	Создание компонентов: progs.biz 

	Microsoft Corporation М59 Разработка Windows-приложений на Microsoft Visual Basic.NET и Microsoft Visual C# -NET. Учебный курс MCAD/MCSD/Пер. с англ. — М.: Издательско-торговый дом «Русская Редакция», 2003. — 512 стр.: ил. 

	Просиз Дж Программирование для Microsoft.NET /Пер. с англ. — М.: Издательско-торговый дом «Русская Редакция», 2003. — 704 стр.: ил.




Приложение А



Текст файла CntrIm.cs



using System;

using System.Collections;

using System.ComponentModel;

using System.Drawing;

using System.Data;

using System.Windows.Forms;

using System.IO;

namespace CmpView

{

/// <summary>

/// Summary description for UserControl1.

/// </summary>

public class CntrIm: System.Windows.Forms.UserControl

{

/// <summary>

/// Required designer variable.

/// </summary>

private System.ComponentModel.Container components = null;

private string Path;

private MyMap mapD;

private bool flag;

private bool flRec;

private int oldX;

private int oldY;

private int curX;

private int curY;

private double oldDx;

private double oldDy;

private Hashtable eventTable = new Hashtable();

public delegate void MapLoadEndDelegate();

public event MapLoadEndDelegate MapLoadEnd;

public CntrIm()

{

// This call is required by the Windows.Forms Form Designer.

InitializeComponent();

mapD = new MyMap();

flag=false;

flRec=false;

// TODO: Add any initialization after the InitComponent call

}

/// <summary>

/// Clean up any resources being used.

/// </summary>

protected override void Dispose(bool disposing)

{

if(disposing)

{

if(components!= null)

components.Dispose();

}

base.Dispose(disposing);

}

protected override void OnPaintBackground(PaintEventArgs pevent)

{

Point [] tmP;

Pen mP= new Pen(Color.Black, 1);	

for (int k=0;k<mapD.Length;k++)

{

tmP = new Point [mapD.AllPaths[k].Length];

for(int j=0;j<mapD.AllPaths[k].Length;j++)

{

tmP[j].X=Convert.ToInt32((mapD.AllPaths[k].PathPoints[j].X*mapD.KX-mapD.DX)*mapD.Scale);

tmP[j].Y=Convert.ToInt32((mapD.AllPaths[k].PathPoints[j].Y*mapD.KY-mapD.DY)*mapD.Scale);					

}

SolidBrush dBr = new SolidBrush(mapD.AllPaths[k].ColorP);

pevent.Graphics.FillPolygon(dBr,tmP);

Pen tPen;

if(mapD.AllPen[k]==null)

{

tPen=new Pen(Color.Black,1);

}

else

{

tPen=mapD.AllPen[k];

}

pevent.Graphics.DrawLines(tPen,tmP);

}

for (int k=0;k<mapD.Length;k++)

{

Point Ctr=new Point();

Ctr.X= Convert.ToInt32((mapD.AllPaths[k].Centr.X-mapD.DX)*mapD.KX*mapD.Scale)-10; 

Ctr.Y=Convert.ToInt32((mapD.AllPaths[k].Centr.Y-mapD.DY)*mapD.KY*mapD.Scale)-10;

Font dF = new Font("Arial", 16);

SolidBrush dB = new SolidBrush(Color.Black);

pevent.Graphics.DrawString(k.ToString(),dF,dB,Ctr);

}

Pen mPp= new Pen(Color.Blue, 1);

pevent.Graphics.DrawLine(mPp,0,0,20,0);

pevent.Graphics.DrawLine(mPp,0,0,0,20);

pevent.Graphics.DrawLine(mPp,0,this.Height,20,this.Height);

pevent.Graphics.DrawLine(mPp,0,this.Height,0,this.Height-20);

pevent.Graphics.DrawLine(mPp,this.Width,0,this.Width-20,0);

pevent.Graphics.DrawLine(mPp,this.Width,0,this.Width,20);

pevent.Graphics.DrawLine(mPp,this.Width,this.Height,this.Width-20,this.Height);

pevent.Graphics.DrawLine(mPp,this.Width,this.Height,this.Width,this.Height-20);

if (flRec==true&&flag==true)

{

Pen myP= new Pen(Color.Blue,1);

pevent.Graphics.DrawLine(myP,oldX,oldY,oldX,curY);

pevent.Graphics.DrawLine(myP,oldX,oldY,curX,oldY);

pevent.Graphics.DrawLine(myP,curX,oldY,curX,curY);

pevent.Graphics.DrawLine(myP,oldX,curY,curX,curY);

}

base.OnPaintBackground (pevent);

}

protected override void OnPaint(PaintEventArgs e)

{

Point [] tmP;			

Pen mP= new Pen(Color.Black, 1); 

for (int k=0;k<mapD.Length;k++)

{

tmP = new Point [mapD.AllPaths[k].Length];

for(int j=0;j<mapD.AllPaths[k].Length;j++)

{

tmP[j].X=Convert.ToInt32((mapD.AllPaths[k].PathPoints[j].X-mapD.DX)*mapD.KX*mapD.Scale);

tmP[j].Y=Convert.ToInt32((mapD.AllPaths[k].PathPoints[j].Y-mapD.DY)*mapD.KY*mapD.Scale);					

//listBox1.Items.Add("x: "+tmP[j].X.ToString());

//listBox1.Items.Add("y: "+tmP[j].Y.ToString());

} 

SolidBrush dBr = new SolidBrush(mapD.AllPaths[k].ColorP);

e.Graphics.FillPolygon(dBr,tmP);

Pen tPen;

if(mapD.AllPen[k]==null)

{

tPen=new Pen(Color.Black,1);

}

else

{

tPen=mapD.AllPen[k];

}

e.Graphics.DrawLines(tPen,tmP);

}

for (int k=0;k<mapD.Length;k++)

{

Point Ctr=new Point();

Ctr.X=Convert.ToInt32((mapD.AllPaths[k].Centr.X-mapD.DX)*mapD.KX*mapD.Scale)-10; 

Ctr.Y=Convert.ToInt32((mapD.AllPaths[k].Centr.Y-mapD.DY)*mapD.KY*mapD.Scale)-10;

Font dF = new Font("Arial", 14);

SolidBrush dB = new SolidBrush(Color.Black);

e.Graphics.DrawString(k.ToString(),dF,dB,Ctr);

}

Pen mPp= new Pen(Color.Blue, 2);

e.Graphics.DrawLine(mPp,0,0,20,0);

e.Graphics.DrawLine(mPp,0,0,0,20);

e.Graphics.DrawLine(mPp,0,this.Height,20,this.Height);

e.Graphics.DrawLine(mPp,0,this.Height,0,this.Height-20);

e.Graphics.DrawLine(mPp,this.Width,0,this.Width-20,0);

e.Graphics.DrawLine(mPp,this.Width,0,this.Width,20);

e.Graphics.DrawLine(mPp,this.Width,this.Height,this.Width-20,this.Height);

e.Graphics.DrawLine(mPp,this.Width,this.Height,this.Width,this.Height-20);

if (flRec==true&&flag==true)

{

Pen myP= new Pen(Color.Blue,1);

e.Graphics.DrawLine(myP,oldX,oldY,oldX,curY);

e.Graphics.DrawLine(myP,oldX,oldY,curX,oldY);

e.Graphics.DrawLine(myP,curX,oldY,curX,curY);

e.Graphics.DrawLine(myP,oldX,curY,curX,curY);

}

base.OnPaint (e);

}

#region Component Designer generated code

/// <summary>

/// Required method for Designer support - do not modify 

/// the contents of this method with the code editor.

/// </summary>

private void InitializeComponent()

{

// 

// CntrIm

// 

this.BackColor = System.Drawing.SystemColors.Control;

this.Name = "CntrIm";

this.Size = new System.Drawing.Size(496, 384);

this.Load += new System.EventHandler(this.CntrIm_Load);

this.SizeChanged += new System.EventHandler(this.CntrIm_SizeChanged);

this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.CntrIm_MouseUp);

this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.CntrIm_MouseMove);

this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.CntrIm_MouseDown);

}

#endregion

private void CntrIm_Load(object sender, System.EventArgs e)

{

}

public void FileOpen(string Pth)

{

mapD = new MyMap();

mapD.Scale=1;

mapD.SetAxes(1,-1);

//mapD.SetXYtoView();

this.Path = Pth;			

StreamReader StrmR = new StreamReader(Path);

bool flag = false;

string str;

int dss=0;

PathD pthD=null;

while (((str = StrmR.ReadLine())!=null))

{

//this.richTextBox1.Text+=str;		

dss++;

if(str=="/$fm 0 def")

{

pthD = new PathD();	

flag=true;

str= StrmR.ReadLine();

dss++;

//this.richTextBox1.Text+=str+"\n";

}

if(flag)

{

int i=0;

string rtt="";

double [] xy=new double [2];

int nm=0;

while (i<str.Length&&str[i]!='C'&&str[i]!='m'&&str[i]!='L')

{

if(str[i]==' ')

{

try

{

xy[nm]=Convert.ToDouble(rtt);

}

catch(System.FormatException e)

{

}

nm++;

if(nm==2)

{

PointD tm = new PointD(xy[0],xy[1]);

pthD.AddPoint(tm);

nm=0;

}

rtt="";

}

else if (str[i]=='.')

{

rtt+=',';

}

else

rtt+=str[i];

i++;

}

}

if((str=="@c"||str=="S")&&flag==true)

{

mapD.AddPath(pthD);

flag=false;

//this.richTextBox1.Text+=str+"\n";

}

}

StrmR.Close();

mapD.SetScaleToView(this.Height,this.Width);

mapD.SetXYtoView();

/* AutoColor AClr = new AutoColor(mapD.AllPaths);

Color [] msc = AClr.GetMsClr;

for (int i=0;i<mapD.Length;i++)

{

this.SetClrPath(i,msc[i]);

}*/

if(MapLoadEnd!=null)

MapLoadEnd(); 

}

private void CntrIm_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)

{

flag = true;

oldX=e.X;

oldY=e.Y;

oldDx=mapD.DX;

oldDy=mapD.DY;

}

private void CntrIm_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)

{

flag=false;

}

private void CntrIm_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)

{	

curX=e.X;

curY=e.Y;

if(flRec==true)

this.Refresh();

}

public void ScaleToPoint(int crX,int crY,double sclN)

{

double x,y;

double oldSc=mapD.Scale;

double oldKx=mapD.KX;

double oldKy=mapD.KY;

mapD.Scale=mapD.Scale*sclN;

x=oldDx-Convert.ToDouble(this.Width)/2/(mapD.Scale*mapD.KX)+Convert.ToDouble(crX)/(oldSc*oldKx);

y=oldDy-Convert.ToDouble(this.Height)/2/(mapD.Scale*mapD.KY)+Convert.ToDouble(crY)/(oldSc*oldKy);	

mapD.SetXYAxes(x,y);

this.Refresh();

}

public void MovePoint(int oldx,int oldy, int crx,int cry)

{

double x,y;

x=oldDx-Convert.ToDouble(crx-oldx)/(mapD.Scale*mapD.KX);

y=oldDy-Convert.ToDouble(cry-oldy)/(mapD.Scale*mapD.KY);

mapD.SetXYAxes(x,y); 

this.Refresh();

}

public void ScaleToRctangle(int oldx,int oldy,int crX,int crY)

{

double scl=0.0;

double x,y;

oldX=oldx;

oldY=oldy;

double oldKx=mapD.KX;

double oldKy=mapD.KY;

double oldSc=mapD.Scale;

int cX,cY;

if(Math.Abs(oldx-crX)>Math.Abs(oldy-crY))

{

scl=Convert.ToDouble(this.Width)/Math.Abs(oldx-crX);

}

else

{

scl=this.Height/Math.Abs(oldy-crY);

}

if(oldx<crX)

{

cX=oldx;

}

else

{

cX=crX;

}

if(oldy<crY)

{

cY=oldy;

}

else

{

cY=crY;

}

mapD.Scale=mapD.Scale*scl;

x=oldDx+Convert.ToDouble(cX)/(oldSc*oldKx);

y=oldDy+Convert.ToDouble(cY)/(oldSc*oldKy);

mapD.SetXYAxes(x,y); 

this.Refresh();

}	

public void VeiwInWindow()

{

mapD.SetScaleToView(this.Height,this.Width);

mapD.SetXYtoView();

this.Refresh();

}

public void RealScale()

{

mapD.Scale=1;

mapD.SetXYtoView();

this.Refresh();

}

public bool VeiwRect

{

set

{

flRec=value;

}

get

{

return flRec;

}

public int GetNamberPath (int crX, int crY)

{

double x,y;

int pth=-1;

x=oldDx+Convert.ToDouble(crX)/(this.mapD.Scale*this.mapD.KX);

y=oldDy+Convert.ToDouble(crY)/(this.mapD.Scale*this.mapD.KY);

double x1,y1,x2,y2;

double p;

double nx;

double ny=y;

int per=0;

for (int k=0;k<mapD.Length;k++)

{

per=0;

for(int j=0;j<mapD.AllPaths[k].Length-1;j++)

{

x1 = mapD.AllPaths[k].PathPoints[j].X;

y1 = mapD.AllPaths[k].PathPoints[j].Y;

x2 = mapD.AllPaths[k].PathPoints[j+1].X;

y2 = mapD.AllPaths[k].PathPoints[j+1].Y;

if(y1!=y2)

{

//this.listBox1.Items.Add("xx");

p=(ny-y2)/(y1-y2);

nx=p*x1+(1-p)*x2;

if((p>0)&&(p<1)&&(x<nx))

{

//if(nx!=x1&&ny!=y1)

per++;

if(nx==x1&&ny==y1)

{

double xp = mapD.AllPaths[k].PathPoints[j-1].X;

double yp = mapD.AllPaths[k].PathPoints[j-1].Y;

if(yp<y1&&y1<y2)

{

per++;

}

else if(y1>y2&&y1>yp)

{

else if(y1==y2&&y1==y)

{

//	per++;

//не входит

} 

}

//listBox1.Items.Add("per_"+per.ToString());

if((per%2)==1)

{

pth=k;

//listBox1.Items.Add("per_"+per.ToString()); 

//listBox1.Items.Add(k.ToString());

}

}

return pth;

}

public void Allocate(int nPth,int type)

{

mapD.Allocate(nPth,type);

this.Refresh();

}

public void SetClrPath (int NPth,Color Clr)

{

mapD.SetClrPth(NPth,Clr);

}

public Color GetClrPath(int NPth)

{

return mapD.AllPaths[NPth].ColorP;

}

public int[,] GetMsGr()

{

int [,] ms = new int [mapD.Length,mapD.Length];

for (int i=0;i<mapD.Length;i++)

{

for(int j=0;j<mapD.Length;j++)

{

ms[i,j]=0;

}

}

double x1,y1;

double _x1,_y1;

for (int i=0;i<mapD.Length;i++)

{

for(int j=0;j<mapD.AllPaths[i].Length;j++)

{

x1 = mapD.AllPaths[i].PathPoints[j].X;

y1 = mapD.AllPaths[i].PathPoints[j].Y;		

for (int k=0;k<mapD.Length;k++)

{

for(int l=0;l<mapD.AllPaths[k].Length;l++)

{

if(i!=k)

{

_x1 = mapD.AllPaths[k].PathPoints[l].X;

_y1 = mapD.AllPaths[k].PathPoints[l].Y;

if(x1==_x1&&y1==_y1)

{

ms[i,k]=1;

}

return ms;

} //getmsgr

public void Run(int curPth, ref int [,] ms)

{

if(ms[curPth,curPth]==0)

{

int j=1;

bool flag = false;

while (j<256&&!flag)

{

flag=true;

for(int i=0;i<ms.GetLength(1);i++)

{

if(curPth!=i&&ms[curPth,i]>0&&ms[i,i]==j)

{

flag=false;

}

}

j++;

}

ms[curPth,curPth]=j-1;

for(int i=0;i<ms.GetLength(1);i++)

{

if(ms[curPth,i]==1&&curPth!=i)

{

ms[curPth,i]=2;

ms[i,curPth]=2;

Run(i,ref ms);

}

private void CntrIm_SizeChanged(object sender, System.EventArgs e)

{

mapD.SetScaleToView(this.Height,this.Width);

mapD.SetXYtoView();

this.Refresh();

}

public MyMap GetMap

{

get

{

return mapD;

}

}

}

}




Приложение Б



Текст файла MyMap.cs



using System;

using System.Drawing;

using System.Drawing.Drawing2D;

namespace CmpView

{

/// <summary>

/// Summary description for MyMap.

/// </summary>

public class MyMap

{

private double scale;

private int length;

private int space;

private double kx;

private double ky;

private double dx;

private double dy;

private PathD [] msPthd;

private Pen [] msPn;

//	private GraphicsPath[] GrphPth;

public MyMap()

{

space=20;

scale =1;

msPthd=null;

msPn=null;

kx=1;

ky=1;

dx=0;

dy=0;

//GrphPth=null;

}

public void AddPath(PathD pathD)

{

PathD[] tmpPathD = new PathD[length+1];

msPn = new Pen[length+1];

for(int i=0;i<length+1;i++)

{

msPn[i]= null;

}

if (length>0)

{

msPthd.CopyTo(tmpPathD,0);

}

tmpPathD[length]=pathD;

msPthd=tmpPathD;

tmpPathD=null;

length++;

/* Point[] tmP = new Point [pathD.Length];

for (int i=0;i<pathD.Length;i++)

{

tmP[i].X=Convert.ToInt32(pathD.PathPoints[i].X*this.scale+this.x*this.scale);

tmP[i].Y=Convert.ToInt32(pathD.PathPoints[i].Y*this.scale+this.y*this.scale);

}	*/

}

public PathD[] AllPaths

{

get

{

return msPthd;

}

}

public Pen[] AllPen

{

get 

{

return msPn;

}

}

public int Length

{

get

{

return length;

}

}

public double Scale

{

get

{

return this.scale;

}

set

{

if(value<10000&&value>0)

{

scale=value;					

}

public void SetAxes(double kX,double kY)

{

kx=kX;

ky=kY;		

}

public void SetXYAxes(double dX, double dY)

{		

dx=dX;

dy=dY;

}

public double KX

{

get

{

return kx;

}

}

public double KY

{

get

{

return ky;

}

}

public double DX

{

get

{

return dx;

}

}

public double DY

{

get

{

return dy;

}

}

public void SetXYtoView()

{

if(length>0)

{

double minX,minY;

minX = kx*(this.msPthd[0].PathPoints[0].X);

minY = ky*(this.msPthd[0].PathPoints[0].Y);

for (int k=0;k<length;k++)

{

for(int j=0;j<msPthd[k].Length;j++)

{

if((kx*msPthd[k].PathPoints[j].X)<minX)

{

minX=kx*msPthd[k].PathPoints[j].X;

}

if((ky*msPthd[k].PathPoints[j].Y)<minY)

{

minY=ky*msPthd[k].PathPoints[j].Y;

}

this.dx=minX/kx-space/(scale*(kx/Math.Abs(kx)));

this.dy=minY/ky-space/(scale*(ky/Math.Abs(ky)));

}

}

public void SetScaleToView(int height,int width)

{

if(length>0)

{

double minX,minY,maxX,maxY;

minX = (kx*this.msPthd[0].PathPoints[0].X);

minY = (ky*this.msPthd[0].PathPoints[0].Y);

maxX = (kx*this.msPthd[0].PathPoints[0].X);

maxY = (ky*this.msPthd[0].PathPoints[0].Y);

for (int k=0;k<length;k++)

{			

for(int j=0;j<msPthd[k].Length;j++)

{

if((kx*msPthd[k].PathPoints[j].X)<minX)

{

minX=kx*msPthd[k].PathPoints[j].X;

}

if((ky*msPthd[k].PathPoints[j].Y)<minY)

{

minY=ky*msPthd[k].PathPoints[j].Y;

}

/////

if((kx*msPthd[k].PathPoints[j].X)>maxX)

{

maxX=kx*msPthd[k].PathPoints[j].X;

}

if((ky*msPthd[k].PathPoints[j].Y)>maxY)

{

maxY=ky*msPthd[k].PathPoints[j].Y;

}

double h,l;

l=Math.Abs(maxX-minX);

h=Math.Abs(maxY-minY);

if(h>l)

{

scale = (height-space*2)/h;

}

else

{

scale = (width-space*2)/l;

}

}

}

public void Allocate(int nPth,int type)

{

if(type ==0)

{

if(msPn[nPth]!=new Pen(Color.Black,4))

{

for(int i=0;i<length;i++)

{

msPn[i]= null;

}

msPn[nPth] = new Pen(Color.Black,4);

}

public void SetClrPth(int NPth, Color Clr)

{

msPthd[NPth].ColorP=Clr;

}