2025测绘程序设计国赛第二次模拟赛|泰森多边形算法

本文主要根据第二次模拟赛的泰森多边形试题进行算法的编写,仅用于记录比赛结果。

一、数据及格式

根据文档提供的数据格式,以及后续算法所需要的数据结构,定义DataCenter类。

static class Data
{
	static public List<Point> RowPoints = new List<Point>();
	static public List<Triangle> Delaunay_Triangles = new List<Triangle>();
	static public List<double> Delaunay_Areas =new List<double>();
	static public List<Point> Hull_pts=new List<Point>();
	static public double Hull_area;
	static public List<Polygon> Vorinoi_Polygon=new List<Polygon>();
	static public List<double> Vorinoi_Areas = new List<double>();
}
public class Point
{
	public double x;
	public double y;
	public Point() { }
	public Point(double x, double y) 
	{
		this.x = x;
		this.y = y;
	}
}
public class Edge
{
	public Point a;
	public Point b;
	public double d;
	public Edge() { }
	public Edge(Point a, Point b)
	{
		this.a = a;
		this.b = b;
		d=Algorithm.get_d(a,b);
	}
}
public class Triangle
{
	//三角形的三个顶点
	public Point a=new Point();
	public Point b=new Point();
	public Point c= new Point();

	//三角形的边
	public List<Edge> edges = new List<Edge>();
	public Point o=new Point();//三角形的外心
	public double r;//三角形外接圆的半径
	public double Area;//三角形的面积
	public Triangle() { }
	public Triangle(Point a, Point b, Point c)
	{
		
		this.a = a;
		this.b = b;
		this.c = c;
		Edge ab=new Edge(a,b);
		Edge ac=new Edge(a,c);
		Edge bc=new Edge(b,c);
		edges.Add(ab);
		edges.Add(ac);
		edges.Add(bc);
		this.Area=get_Area(edges);
		this.o = get_O();
		this.r=Algorithm.get_d(a,o);
	}
	//利用垂直平分线交点求取三角形外心
	public Point get_O()
	{
		
		double d = 2 * (a.x * (b.y - c.y) +b.x * (c.y - a.y) +c.x * (a.y - b.y));

		if (Math.Abs(d) < 1e-10)
		{
			// 如果 d≈0,说明三点几乎共线
			return null;
		}

		double ux = ((a.x * a.x + a.y * a.y) * (b.y - c.y) +
					 (b.x * b.x + b.y * b.y) * (c.y - a.y) +
					 (c.x * c.x + c.y * c.y) * (a.y - b.y)) / d;

		double uy = ((a.x * a.x + a.y * a.y) * (c.x - b.x) +
					 (b.x * b.x + b.y * b.y) * (a.x - c.x) +
					 (c.x * c.x + c.y * c.y) * (b.x - a.x)) / d;

		Point o=new Point( ux, uy);
		return o;
	}
	//海伦公式求取三角形面积
	public double get_Area(List<Edge> edge)
	{
		double ab = edge[0].d;
		double ac = edge[1].d;
		double bc = edge[2].d;
		double s = (ab + ac + bc) / 2;
		double area = Math.Sqrt(s*(s-ab)*(s-ac)*(s-bc));
		return area;

	}
}
public class Polygon
{
	public List<Point> points;
	public double Area;
	public Polygon(List<Point> points) 
	{
		this.points = points;
	    this.Area = Algorithm.Polygon_Area(points); 
		
	}
}

二、主要算法

主要展示核心算法部分,文件管理及绘图部分省略。

(一)Delaunay 三角剖分

试题中提供Bowyer-Watson 算法流程进行Delaunay 三角剖分,并使用海伦公式求取每个Delaunay三角形面积,进行升序排序。

static public void Delaunay_BW()
{
	//超级三角形的构建
	double minX = points.Min(p => p.x);
	double maxX = points.Max(p => p.x);
	double minY = points.Min(p => p.y);
	double maxY = points.Max(p => p.y);

	double dx = maxX - minX;
	double dy = maxY - minY;
	double deltaMax = Math.Max(dx, dy) * 100; // 放大 100 倍,确保足够大

	Point p1 = new Point(minX - deltaMax, minY - deltaMax);
	Point p2 = new Point(minX - deltaMax, maxY + deltaMax * 3);
	Point p3 = new Point(maxX + deltaMax * 3, minY - deltaMax);

	Triangle supertriangle =new Triangle(p1,p2,p3);
	triangles.Add(supertriangle);
	foreach (Point p in points)
	{
		//标记坏三角形
		List<Triangle> bad_triangles=new List<Triangle>();
		foreach (Triangle triangle in triangles)
		{
			Point center = triangle.o;
			double r=triangle.r;
			double d=get_d(p,center);
			if (d <=r) bad_triangles.Add(triangle);
		}
		//构造边界多边形polygon
		Dictionary<string,Edge> edgeCount=new Dictionary<string,Edge>();
		foreach(Triangle tr in bad_triangles)
		{
			foreach(Edge edge in tr.edges)
			{
				string key=get_key(edge);
				if (edgeCount.ContainsKey(key)) edgeCount.Remove(key);
				else edgeCount[key] = edge;
			}
		}
		List<Edge> polygon=edgeCount.Values.ToList();
		//移除坏三角形
		foreach(Triangle tr in bad_triangles) triangles.Remove(tr);
		//构造新的三角形
		foreach(Edge edge in polygon)
		{
			if (IsCollinear(edge.a, edge.b, p)) continue;
			else
			{
				triangles.Add(new Triangle(edge.a, edge.b, p));
			}
		}
	}
	//移除所有和超级三角形相关的三角形
	triangles.RemoveAll(t => t.a == p1 || t.a == p2 || t.a == p3 ||
					   t.b == p1 || t.b == p2 || t.b == p3 ||
					   t.c == p1 || t.c == p2 || t.c == p3);
	//存储Delaunay三角剖分结果
	Data.Delaunay_Triangles=triangles;
	//将三角形面积按照升序排序并保存
	Data.Delaunay_Areas= triangles.Select(t => t.Area).OrderBy(a => a).ToList();
}

(二)凸包计算

试题根据Andrew's Monotone Chain 算法进行凸包的计算,并使用鞋带公式计算凸包面积

static public void ConvexHull_AWC()
{
	//对点集合points进行排序,并初始化凸包序列
	points = points.OrderBy(p => p.x).ThenBy(p => p.y).ToList();
	List<Point> lower = new List<Point>(), upper = new List<Point>();
	//构建下凸包
	foreach(Point p in points)
	{
		while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
			lower.Remove(lower[lower.Count - 1]);
		lower.Add(p);
	}
	//构建上凸包
	for (int i = points.Count-1; i >= 0 ; i--) 
	{ 
		Point p = points[i];
		while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
			upper.Remove(upper[upper.Count - 1]);
		upper.Add(p);
	}
	//合并上下凸包,得到逆时针顺序的凸包点集
	lower.RemoveAt(lower.Count-1);
	upper.RemoveAt(upper.Count-1);
	Data.Hull_pts=lower.Concat(upper).ToList();
	//计算凸包面积
	Data.Hull_area=Polygon_Area(Data.Hull_pts);
}

(三)Vorinoi图计算

试题主要构建封闭的泰森多边形,并计算其面积进行升序排序。

static public void Voronoi()
{   
	HashSet<Point> hull_pts=new HashSet<Point>(Data.Hull_pts);
	List<Polygon> polygons=new List<Polygon>();
	List<Polygon> open_polygons=new List<Polygon>();
	List<double> areas=new List<double>();
	foreach(Point p in points)
	{
		List<Triangle> tri_indices = new List<Triangle>();
		List<Point> circumcenters=new List<Point>();
		//排除凸包上的点
		if (hull_pts.Contains(p)) continue;
		//获取该点关联的三角形索引列表
		tri_indices = Data.Delaunay_Triangles.Where(t=>t.a==p||t.b==p||t.c==p).ToList();
		//获取该点关联的所有三角形的外心
		circumcenters = tri_indices.Select(t=>t.o).ToList();
		//如果外心数量小于3则无法构成多边形,跳过
		if(circumcenters.Count<3) continue;
		//以 circumcenters 列表中所有点的平均坐标为参考点
		Point bar =new Point(circumcenters.Average(o=>o.x), circumcenters.Average(o => o.y));
		//对circumcenters 列表进行极角排序
		circumcenters=circumcenters.OrderBy(o => Math.Atan2(o.y - bar.y, o.x - bar.x)).ToList();
		//对多边形进行检查
		if (IsPointInsidePolygon(p, circumcenters))
		{
			polygons.Add(new Polygon(circumcenters));
			areas.Add(Polygon_Area(circumcenters));
		}
	}
	Data.Vorinoi_Polygon=polygons;
	Data.Vorinoi_Areas=areas.OrderBy(area=>area).ToList();					
}

(四)辅助函数

//使用射线法判断点是否位于封闭多边形内部
static public bool IsPointInsidePolygon(Point p, List<Point> polygon)
{
	int crossings = 0;
	for (int i = 0; i < polygon.Count; i++)
	{
		Point a = polygon[i];
		Point b = polygon[(i + 1) % polygon.Count];
		if (((a.y > p.y) != (b.y > p.y)) &&
			(p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x))
		{
			crossings++;
		}
	}
	return (crossings % 2) == 1;
}
//鞋带公式求取多边形面积
static public double Polygon_Area(List<Point> points)
{
	double area=0;
	for (int i = 0; i < points.Count; i++)
	{
		int j = (i + 1) % points.Count;
		area +=Math.Abs(points[i].x * points[j].y - points[j].x * points[i].y);
	}
	return area;
}
//定义向量的叉积
static public double Cross(Point a, Point b, Point c)
{
	return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}
//判断三个点是否共线
static public bool IsCollinear(Point p1, Point p2, Point p3)
{
	double area = p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y);
	return Math.Abs(area) < 1e-10;
}
//获取边的唯一索引值
static public string get_key(Edge edge)
{
	Point p1 = edge.a;
	Point p2 = edge.b;
	if (p1.x < p2.x || (Math.Abs(p1.x - p2.x) < 1e-10 && p1.y < p2.y))
		return $"{p1.x}-{p1.y}-{p2.x}-{p2.y}";
	else
		return $"{p2.x}-{p2.y}-{p1.x}-{p1.y}";
}
//判断两条边是否相等
static public bool issame(Edge edge1,Edge edge2) 
{
	if (edge1.a == edge2.a && edge1.b == edge2.b)
	{
		return true;
	}
	else if (edge1.a == edge2.b && edge1.b == edge2.a)
	{ 
		return true;
	}
	return false;
}
//计算两点间的距离
static public double get_d(Point a, Point b)
{
	double left = Math.Pow(a.x - b.x, 2);
	double right = Math.Pow(a.y - b.y, 2);
	double d = Math.Sqrt(left + right);
	return d;
}

三、结果展示

(一)数据导入

(二)计算结果

(三)Delanuay三角剖分结果

(四)封闭泰森多边形绘图

泰森多边形绘图部分,因泰森多边形算法中只计算并记录了封闭的泰森多边形,故仅绘制出封闭多边形部分。位于凸包上的点的泰森多边形是开放的,暂未研究明白如何计算并绘制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值