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