如何求一个平面区域中心点问题--编程实现

再做一个项目的时候,想到了这样一个问题:
涉及到使用百度地图,在地图中随机的选取任意多个区域,如何求出这些区域的一个中心点坐标;就是在若干个区域中,找出最中心的一个区域,用于展示到用户屏幕的最中心处。
上面的问题,简化成数学问题就是,在一个平面区域中,散落n的点 P 1 − P n P_1-P_n P1Pn(n>0),求在这个平面区域(二维坐标系)上这些散落点组成的最少区域的中心点C,找到中心点后,在求出这若干点离中心点最近的点 P l P_l Pl.

对于上面问题,先不用证明问题是否可解,因为这就是编程问题,不用在数学上严格的去证明;

  • 假设问题是有解的
  • 用计算机编程如何实现
  • 如何更高效,更简洁的实现,当散落的点越来越多,构成的区域多边形越来越复杂,如何提供更加高效算法
  • 考虑区域点密度加权1,算出更加优化的区域“中心点”

问题解

下面进行问题解分析,(分析过程几乎可以证明问题是否可解了)
我只提供一种分析思路,不对的话请见谅

  1. 首先,定义一个点 Gps(lon,lat),然后将若干个点用集合List包装起来 List<Gps>;
  2. 甭管用什么方法,总是可以从list中取出一个点来(可以取第一个,也可以取最后一个,或者中间位置的一个或者随机一个),把它设置为中心点;
  3. 然后用剩下的点和中心点做距离比较,总能找到一个最大距离 D 1 D_1 D1,将这个距离保存下来;
  4. 然后重复从list取另外一个点作为中心点(比如,第一次取第一个,第二次取第二个),再跟其余点做距离比较,那么也会得到一个最大距离 D 2 D_2 D2,保存;
  5. 以此类推,重复步骤2,3,会得到一系列的最大距离 D 3 , D 4 , . . . , D n D_3,D_4,...,D_n D3,D4,...,Dn,并保存;
  6. 从这n个距离集合中,取最少距离的那个即为中心点,完毕。

上面过程中保存距离,应该使用Map这种数据结构,key为被选中的点,value为最大距离;然后从map中选出value最少的那个key。

上面的解决方案,基本上可以满足:用计算机编程如何实现这一条了,编程的话应该很easy;

现在考虑的是,这种过程是否高效简洁?

不好说,因为没有对比的其他解决方法,现在分析一下上面算法特点:

  • 只涉及到加减法,这点应该是优势,不需要太多的数学理论
  • 不断的循环,当n较少时,会很有利,当n不断增大后,可能循环多了就没意义了,甚至无法计算
  • 这个算法解决不了,上面提到的“密度加权”问题

现在再说一下,为什么在最长距离集合中距离最短的,就是最中心点了?

这个最好有数学系的同学给证明一下,我也只是通过观察发现,可能最短就是中心点,用这种方式找出来的点是可能有多个中心点的,下面给大家分析一下

首先从最简单的开始,只有一个点,那么这个点就是中心点,这是事实;
两个点,那么任意两个都可作为中心点,也是事实;
三个点,假设三个点的距离都相等,那么这三个点任意一个可以作为中心点,如果三条边不相等,如图
三个坐标点找中心点
对于ABC三个点,分别做连线,得到AB,AC,BA,BC,CA,CB,需要保证找到每个点的最大距离,那么会得到 A: AC,B:BC,C:CA;另外还要保证最大距离的半径下的圆形区域内将所有点包含,显然AC=CA>BC,那么就能选出B作为中心点;

可以再取四个点的区域,用上面的方法分析,首先正方形的四个点,会发现对角线是为最大距离,并且每个点的最大距离都相等,那么这是个点任意一个可以作为中心点;其他的任意四个点,也是总能找到某个点的最大距离,并且以这个距离为半径做圆形可以将四个点都包含在内,那么这个点就是中心点;

编程实现

前面已经进行了算法描述,下面接着编程如何实现,并测试效果

首先需要引入一个坐标类,用于标识某个点,并提供一个计算两点间距离的方法

类Gps

package com.iscas.tools;

/**
 * 地理坐标 Gps <br>
 * 
 * @author 王俊伟 wjw.happy.love@163.com
 * 
 * @version 1.0.0
 */
public class Gps {

	/** 纬度 */
	public double lat;
	/** 经度 */
	public double lon;

	public Gps(double lon, double lat) {
		this.lat = lat;
		this.lon = lon;
	}

	/**
	 * 求当前点跟目标点的距离
	 * 
	 * @param gps
	 *            目标点
	 * @return 距离
	 */
	public double distance(Gps gps) {
		if (this.equals(gps)) {
			return 0d; // 同一个点距离为0
		}
		return EarthUtil.getDistance(this.lon, this.lat, gps.lon, gps.lat);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		Gps gps = (Gps) obj;
		if (gps.lon == this.lon && gps.lat == this.lat) {
			return true;
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "(" + this.lon + "," + this.lat + ")";
	}

	public void print() {
		System.out.println(this);
	}

算法实现

然后看一下具体的算法实现部分,非常简单

/**
* 根据一组坐标选取中心点坐标
 * 
 * @param gpses
 *            坐标集合
 * @return 中心点坐标
 */
public static Gps pickCenter(List<Gps> gpses) {
	Gps g = null;// 目标中心点
	Map<Gps, Double> maxDistances = new HashMap<>();
	List<Double> distances = new ArrayList<>();
	for (Gps center : gpses) {// 遍历每个中心点
		for (Gps gps : gpses) {
			distances.add(center.distance(gps));
		}
		Collections.sort(distances, Collections.reverseOrder());//逆序,需要取最大的距离
		maxDistances.put(center, distances.get(0));
		distances.clear();
	}
	// 对maxDistances,根据value排序,取距离最小的
	g = maxDistances.entrySet().stream().sorted(Comparator.comparingDouble(e -> e.getValue())).map(m -> m.getKey())
			.collect(Collectors.toList()).get(0);
	return g;
}

上面算法比较简洁,原理就是,以某个点为中心,画同心圆,最后能在这些点中找到一个(或若干个)最短半径构成的圆形区域能恰好包含这些所有的点,这个圆形的中心点就是我们所需要的中心点。

测试

下面是我找了5组测试数据,分别测试上次,记录耗时,另外说明一下点的生成为随机生成 lon的范围[0,180),lat范围[0,90)

点数(个)耗时(ms)点数(个)耗时(ms)点数(个)耗时(ms)
10075100124100109
100070810007681000822
1000061,6711000060,1851000060,661
100000至少半个小时还没结果,kill了1000006546799100000实在不敢跑了
1000000上面的还没跑完呢,double kill10000001000000

原本对设计时候对10万个点还是有信心的,没想到结果,真是一言难尽啊!
说一下我电脑配置吧:内存12G(这个肯定够了),cpu Intel i5-4460 3.20GHz 4核,结果在一万个点就掉链子了,这算法真是不测不知道,一测吓一跳,虽然前面已经分析过,随着点数增多,会有性能问题…
实际上在跑到1万个点测试的时候,就已经是令人不可接受了,一万个点大概需要1分钟才能找到中心点,在实际应用中我得项目最大承载量是需要在1万5千个点中找到中心点。
仔细想想,结合算法,对于1万个点,仅仅求任意两点的距离就需要计算1亿次,这里面第一次排序还需要执行1万次,监测可以发现1个点内排序一次大概1-2ms,1万次;对10万个点,内排序一次在20ms左右,也需要1万次。
这样再计算一下,对于10万个点,内排序就需要 ((20*100000)/1000 )/ 60 = 33min!
外排序较快172毫秒完成。
一百万个点的时候,一次内排序耗时360毫秒左右,这个时间真的耗费不起。

这个算法应该说对于项目来说是不符合预期的,还需要优化!

结语

上面已经提供了一套解决此问题的算法,但是算法并不完美,对于加权处理还不够;希望有大神能够提供更加完善的证明获取其他算法。
另外,上面全是基于二维平面,实际还可以扩展到三维空间,用上面的方式一样能解决问题。



  1. 点密度加权:平面区域内,散落的点呈现某种趋势,如一些区域稠密,一些区域稀疏等,对于稠密区权重要高于稀疏区,考虑到此条件下,找到的中心点应该偏向与稠密区域 ↩︎

<think>好的,用户问的是UG二次开发中如何求一个平面的质心。我需要先回忆一下UG二次开发的相关知识,特别是关于几何计算的部分。 首先,质心,也就是几何中心,通常指的是平面图形的形心。对于规则的形状,比如矩形或者圆形,可以直接用公式计算。但UG中的平面可能是任意复杂的二维面,所以可能需要用到积分或者分解成简单图形的方法。 在UG二次开发中,通常使用NXOpen API来进行操作。用户可能需要通过编程方式获取平面的几何信息,然后进行计算。可能的步骤包括: 1. 获取平面的边界或轮廓:可能需要遍历平面的边,获取所有边界的曲线,然后组合成一个闭合区域。 2. 分解为简单几何元素:比如将复杂面分解为多个三角形或四边形,分别计算它们的面积和质心,再进行加权平均。这种方法比较常见,因为分解后计算相对简单。 3. 使用数值积分方法:对于无法分解的复杂形状,可能需要用数值积分来近似计算面积和质心坐标。这涉及到将区域分割成小区域,计算每个小区域的贡献,然后累加。 接下来,我需要考虑UG的API中是否有直接获取质心的方法。可能没有直接的函数,所以需要自己实现计算逻辑。例如,对于面对象(NXOpen.Face),可能需要获取其几何表示,然后处理参数域内的积分。 假设用户使用的是C#或VB.NET进行开发,可能需要用NXOpen.UF模块中的函数。例如,使用UFSession.Modl类中的函数来评估面的几何信息。 具体步骤可能包括: - 获取面的参数范围(U参数和V参数)。 - 对参数区域进行网格划分,生成采样点。 - 在每个采样点处计算面的三维坐标和法向量,进而计算面积元素。 - 积分计算总面积和质心的坐标。 另外,如果是通过面的边界曲线来计算,可能需要将面投影到某个平面(如UV平面),转换为二维问题,然后用多边形质心的公式计算。 不过,这些步骤可能比较复杂,特别是对于非平面曲面,但用户的问题是关于平面的,所以可能可以简化。如果平面是平的,可以将其投影到二维坐标系,用多边形面积和质心公式计算。 例如,对于多边形,质心公式是各顶点坐标乘以对应面积分量的平均值。具体公式是: C_x = (1/(6A)) * Σ (x_i + x_{i+1})*(x_i y_{i+1} - x_{i+1} y_i) C_y = (1/(6A)) * Σ (y_i + y_{i+1})*(x_i y_{i+1} - x_{i+1} y_i) 其中A是多边形面积。 所以在UG中,可能需要先获取平面的边界环,提取所有顶点坐标,然后应用上述公式。 总结下来,步骤可能包括: 1. 获取平面的边界环(Edge Loop)。 2. 提取边界环中的边(Edges),遍历边获取顶点坐标。 3. 将这些顶点坐标投影到二维平面(因为平面是平的,可以选一个局部坐标系)。 4. 应用多边形质心公式计算质心。 5. 将质心坐标转换回三维空间。 需要注意的是,边可能不是直线段,而是曲线,这时候可能需要参数化曲线,采样点来近似。或者确保平面边界由直线组成,否则处理曲线边会更复杂。 可能还需要处理多个环的情况,比如平面中间有孔。这时候需要将外环和内环分开处理,计算它们的面积和质心,再合并结果。 另外,UG的API中是否有直接获取面的顶点坐标的函数?可能需要检查Edge的类型,如果是直线边,可以直接获取起点和终点;如果是圆弧或其他类型,可能需要采样或积分。 总之,这个过程涉及几何处理、坐标转换和数值计算,可能需要一定的数学和编程知识。用户需要具备使用NXOpen API的经验,以及对几何模型的理解。</think>在UG NX二次开发中,求平面的质心需要结合几何分析和API操作。以下是分步实现方法: --- ### **步骤1:获取平面的几何边界** - 通过`NXOpen.Face`对象获取面的所有边界环(`EdgeLoop`)。 - 区分外环和内环(孔洞),通常外环面积最大。 ```csharp // 伪代码示例(C#) Face face = ...; // 获取目标平面 EdgeLoop[] edgeLoops = face.GetEdgeLoops(); // 遍历edgeLoops,通过面积判断外环和内环 ``` --- ### **步骤2:提取边界顶点坐标** - 遍历边(`Edge`)并提取几何数据: - 直线边:直接获取起点和终点。 - 曲线边:参数化采样或解析方程(需处理复杂情况)。 ```csharp List<Point3d> vertices = new List<Point3d>(); foreach (Edge edge in edgeLoop.GetEdges()) { if (edge.SolidEdgeType == Edge.EdgeType.Line) { LineEdge lineEdge = edge as LineEdge; vertices.Add(lineEdge.StartVertex.Point); vertices.Add(lineEdge.EndVertex.Point); } else { // 对曲线边进行采样近似 // 使用edge.EvaluatePoints()或解析参数方程 } } ``` --- ### **步骤3:投影到局部二维坐标系** - 将三维顶点投影到平面局部坐标系(UV平面): - 使用`face.GetUVBounds()`获取参数范围。 - 通过`face.Evaluate()`将三维点转换为UV参数。 ```csharp Matrix3x3 coordinateSystem = face.GetCoordinateSystem(); List<Point2d> uvPoints = new List<Point2d>(); foreach (Point3d pt in vertices) { double[] uv = new double[2]; face.Evaluate(pt, uv); uvPoints.Add(new Point2d(uv[0], uv[1])); } ``` --- ### **步骤4:计算多边形质心(形心)** - **公式**: $$ C_x = \frac{1}{6A} \sum_{i=0}^{n-1} (x_i + x_{i+1})(x_i y_{i+1} - x_{i+1} y_i) $$ $$ C_y = \frac{1}{6A} \sum_{i=0}^{n-1} (y_i + y_{i+1})(x_i y_{i+1} - x_{i+1} y_i) $$ $$ A = \frac{1}{2} \sum_{i=0}^{n-1} (x_i y_{i+1} - x_{i+1} y_i) $$ - **代码实现**: ```csharp double sumX = 0, sumY = 0, area = 0; int n = uvPoints.Count; for (int i = 0; i < n; i++) { int j = (i + 1) % n; double xi = uvPoints[i].X, yi = uvPoints[i].Y; double xj = uvPoints[j].X, yj = uvPoints[j].Y; double factor = xi * yj - xj * yi; area += factor; sumX += (xi + xj) * factor; sumY += (yi + yj) * factor; } area /= 2; double centroidU = sumX / (6 * area); double centroidV = sumY / (6 * area); ``` --- ### **步骤5:将质心转换回三维空间** - 使用`face.Evaluate()`将UV坐标映射回三维点。 ```csharp Point3d centroid3d; face.Evaluate(new double[] { centroidU, centroidV }, out centroid3d); ``` --- ### **注意事项** 1. **复杂边界处理**: - 若边界包含曲线(如圆弧、样条),需通过采样或解析公式计算近似顶点。 2. **多环处理**: -平面有内环(孔洞),需计算外环与内环的面积差,并调整质心公式。 3. **API函数**: - 使用`UFSession.Modl`中的函数(如`UF_MODL_ask_face_props`)可直接获取面的属性,但需验证是否包含质心数据。 --- ### **简化方法(若平面为规则多边形)** - 直接调用`NXOpen.UF.UFSession.GetModl().AskMassProps()`计算质量属性(需设置密度为1,厚度为0)。 ```csharp UFSession ufs = UFSession.GetUFSession(); double[] centroid = new double[3]; double[] moments; double[] products; ufs.Modl.AskMassProps(new Tag[] { face.Tag }, 1, 0, out centroid, out moments, out products); ``` --- 通过以上步骤,即可在UG二次开发中准确计算平面的质心。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

junehappylove

急急如律令,buibui~~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值