手写地理信息组件系列 第1篇空间对象的表示和简单绘制难度指数:★☆☆☆☆
各位看官老爷好!从此一篇起,我将会陆续更新“手写地理信息组件”系列。目的在于和广大GIS爱好者和从业者,讨论关于GIS底层的相关内容。力图用一种轻松的氛围,和最纯粹的代码形式表现GIS软件的底层逻辑。让地理信息从业者不再囿于商业软件的二次开发,更理性的看待商软。愿与诸共同进步,懂点儿底层,不做调包侠。
开发环境介绍
实现语言:C#
开发工具:VisualStudio,版本不限。
C#原生对窗口图形显示支持较好,虽然界面效果十分朴素,但是使用简单,开发难度小,对于图形开发,只要掌握图形理论知识就能很快上手。做到不纠结于具体语言,让GIS相关知识真正沉淀下来。
空间图形的构成
我们知道,不论是一维二维三维空间要素,都可以用点坐标来描述。线可以由两点表示,面可以由三个及以上的平面坐标点表示,空间体可以由四个及以上的空间坐标点表示。由此可见,空间实体可以由最基本的点来表示。
严格的讲,点实体(point)是一种抽象概念,不仅代指点状对象,而是可以被抽象为点的一切空间实体。如市区内的一座楼,楼上向下望的每个小脑袋,甚至这座楼所在的城市,都可以被抽象为一个点实体。
不同于点实体的抽象概念,节点(vertex)是构成如线实体,面实体这样空间实体对象(feature)的实际单元。我们在地图上绘制一个三角形时,可以通过点取三个点坐标就能完成绘制,那么这三个点就可以称为这个面实体的三个节点。事实上,面图形实际存储的也就是这三个节点的有序坐标串。
空间图形的代码表示
明确了空间图形的组成,下面就以C#代码实现一个简单的地图程序。
首先建立一个C#工程,选择windows窗体程序。命名为MiniGIS,新建类文件GISEntities,在内构建空间实体最基本的属性。
1namespace MiniGIS
2{
3 //节点
4 class Vertex
5 {
6 double x;
7 double y;
8 }
9 //点实体
10 class Point
11 {
12 Vertex Location;
13 }
14 //线实体
15 class Line
16 {
17 List Vertexs;18 }19 //面实体20 class Polygon21 {22 List Vertexs;23 }24}
OK,空间实体的基本定义已经完成。下面来具体落实,图形怎么画的问题。
图形的绘制和查询
现以最基本的点绘制为例,实现图形和属性的绘制,鼠标点选图形的查询。
首先对实体类进行必要的属性和方法扩展,使其实现绘制方法。
1class Vertex
2{
3 public double x;
4 public double y;
5
6 //扩展构造函数
7 public Vertex(double x, double y) 8 {
9 this.x = x;
10 this.y = y;
11 }
12
13 //扩展计算两点距离方法(点选查询时会用)
14 public double Distance(Vertex another)15 {
16 //勾股定理求两点距离
17 return Math.Sqrt(Math.Pow(x - another.x, 2) + Math.Pow(y - another.y, 2));
18 }
19}
20
21//点实体
22class Point
23{
24 private Vertex location;
25 private String attrbute;
26
27 public Point(Vertex vertex, String attr)28 {
29 this.location = vertex;
30 this.attrbute = attr;
31 }
32
33 ///
34 /// 绘制图形
35 ///
36 /// C#绘图类
37 public void DrawShape(System.Drawing.Graphics graphics)38 {
39 //画笔为实心红色画笔,绘制5*5像素的点
40 graphics.FillEllipse(new SolidBrush(Color.Red), new Rectangle((int)location.x, (int)location.y, 5, 5));
41 }
42 //绘制属性
43 public void DrawAttrbute(Graphics graphics)44 {
45 graphics.DrawString(attrbute, new Font("宋体", 20), new SolidBrush(Color.Blue), new PointF((int)location.x, (int)location.y));
46 }
47 //计算两点距离
48 public double Distance(Vertex another)49 {
50 return location.Distance(another);
51 }
52}
设计一个简单界面,主要由三个TextBox和一个Button组成,三个输入框textBox_X、textBox_Y、TextBox_Attr分别代表待显示点的x坐标,y坐标和属性。
现在双击button按钮注册点击事件,在按钮点击事件代码块中书写上述内容的调用逻辑。
1public partial class Form1 : Form
2{
3 //已输入点集合
4 List points = new List(); 5 6 public Form1() 7 { 8 InitializeComponent(); 9 }1011 private void button1_Click(object sender, EventArgs e)12 {13 //获取输入14 double x = Convert.ToDouble(textBox_X.Text);15 double y = Convert.ToDouble(textBox_Y.Text);16 String attr = textBox_Attr.Text;1718 //实例化节点19 Vertex vertex = new Vertex(x, y);2021 //实例化点实体22 Point p = new Point(vertex, attr);2324 //获取当前Form的Graphics(即窗口背景),并绘制25 Graphics graphics = this.CreateGraphics();26 p.DrawShape(graphics);27 p.DrawAttrbute(graphics);28 points.Add(p);29 }30}
现在启动项目,输入两组坐标13,45和55.6,80及其属性,现在可以查看效果
当然,绘制和显示并不足以构成一个基本的地图应用,下面加入图形点选的属性查询。
属性查询的实现思路是获取鼠标坐标,匹配图上与之最近的坐标点,如果与最近坐标点的距离小于设定的阈值(Threshold),可以认为该点已被选取,相应地查找该点的Attrbute。
实现鼠标点选,需要事先注册窗体的MouseClick事件。
1//距离容限值 10 像素
2const int ThresholdDistance = 10;
3
4private void Form1_MouseClick(object sender, MouseEventArgs e) 5{
6 Vertex vertex = new Vertex(e.X, e.Y);
7 double minDistance = Double.MaxValue;
8 Point nearest = null;
9
10 //筛选与鼠标点最近的坐标点
11 foreach (Point p in points)
12 {
13 double distance = p.Distance(vertex);
14 if (distance 15 {
16 nearest = p;
17 minDistance = distance;
18 }
19 }
20
21 //小于阈值,弹出属性
22 if (nearest != null && nearest.Distance(vertex) 23 MessageBox.Show(nearest.attrbute);
24}
下面运行看东西:
点选查询已经实现。
OK,我们手写的第一个地图应用就完成了!功能非常纯粹,体积非常小巧(11k…)。虽然这个迷你GIS应用代码量少,逻辑也很简单,但是它暴露了节点(Vertex)这一重要,却在二次开发中并不常用的概念。
我们在产品或项目中更多的调用如Point,Polygon等Geometry层面的包装对象,因其方便的解析调用而更少的使用Vertex概念,因为其更靠近底层。
好了,这一篇的内容就是这些了。如果你自己有动手能力,可以自己手动做一下本篇这个“GIS小玩具”,或者尝试自己增加逻辑,甚至应用啊:),比如这个插入星星,查询星星什么的。。。
当然,点击添加星星后可以注掉一行代码,不显示名称,点选之后再显示。这里为了动图好看就加上了。把星空做背景确实好看些,不过若是使用也确实蛮弱智的。。
看好关注,下期见!