在博客园学习一段时间的C#了,一直想写点东西作为回报,可惜自己懂得少,能写出来的大部分网上都能搜到现成。正好这段时间帮同事写了个地图离散点找外接凸多边形的实现,网上大概搜了下好像没有,不如分享出来大家一起看看。
先说下基本逻辑。1,找到所有点集中纬度最高的点作为多边形的起点。2,遍历其余点找到第二个点,使得除这两点外的其他所有点都在这两点连线的同一侧。3,继续寻找第三个,第四个点,直到找到的某一个点就是最北边的点,多边形闭环形成。
需要注意的地方是,第一个和第二个点简单,从第三个点开始需要判断找到的点不能已经存在于列表中。对照图片可能说的更清楚一些,如图a,b,c依次为多边形顶点。仅判断所有点在同一侧的话从b点会找到a和c都满足,因此需要判断找到的点不能是上一个找到的点。下面代码处会写说明。
先定义坐标点类,主要成员经度,纬度。另外重载相等和不等运算符,以及Equals和GetHashCode方法。
class Location { private double longitude; private double latitude; public Location(double _longitude, double _latitude) { longitude = _longitude; latitude = _latitude; } /// <summary> /// 经度 /// </summary> public double Longitude { get { return longitude; } set { longitude = value; } } /// <summary> /// 纬度 /// </summary> public double Latitude { get { return latitude; } set { latitude = value; } } public static bool operator ==(Location lhs, Location rhs) { return lhs.Latitude == rhs.Latitude && lhs.Longitude == rhs.Longitude; } public static bool operator !=(Location lhs, Location rhs) { return !(lhs == rhs); } public override bool Equals(object obj) { if (obj == null) { return false; } if ((obj.GetType().Equals(this.GetType())) == false) { return false; } Location temp = null; temp = (Location)obj; return this.Latitude.Equals(temp.Latitude) && this.Longitude.Equals(temp.Longitude); } public override int GetHashCode() { return this.Latitude.GetHashCode() + this.Longitude.GetHashCode(); } }
再定义FindPolygon类,并用坐标点集合list来初始化对象。
class FindPolygon { private List<Location> list; /// <summary> /// 用点集list初始化对象 /// </summary> /// <param name="_list"></param> public FindPolygon(List<Location> _list) { list = _list; } }
先写出来找点的上层逻辑,底层再根据需要逐步完成。从最北边的点开始找下一个点,直到找到下一个点又回到最北边的点形成闭环为止。
/// <summary> /// 从最北边的点开始找下一个点,使其余所有点在这个点和下个点的连线的同一侧 /// </summary> /// <returns>外接多边形顶点集合</returns> public List<Location> GetPolygon() { List<Location> result = new List<Location>(); //最北边的点作为多边形第一个点 Location a = NorthernMost(); Location b = NorthernMost(); result.Add(b);//add第一个点 Location c = GetNextPoint(b, b);//第一次找点特殊处理 while (!NorthernMost().Equals(c)) { a = b; b = c;//交换abc三个点,继续找下一个点 result.Add(c); c = GetNextPoint(b, a); } return result; }
写完上一段代码,发现 NorthernMost()和 GetNextPoint() 方法需要继续完成,这里就需要判断根据第一个点找第二个点,但找到的不能是第〇个点了。
/// <summary> /// 找到最北边的点为多边形的第一个点 /// </summary> /// <returns></returns> private Location NorthernMost() { Location lo = list[1]; foreach (var l in list) { if (l.Latitude > lo.Latitude) lo = l; } return lo; } /// <summary> /// 给定一个点获取另一个点,使得列表中其他点都在两点连线同一侧,但是找到的这个点不能是上一个点 /// </summary> /// <param name="_currentpoint">给定这个点</param> /// <param name="_lastpoint">上一个点</param> /// <returns></returns> private Location GetNextPoint(Location _currentpoint, Location _lastpoint) { foreach (Location l in list) { if (!_currentpoint.Equals(l)) { if (AtOneSide(_currentpoint, l) && !_lastpoint.Equals(l)) return l; } } return null;//这个地方是走不到的 }
继续写完上述代码,发现AtOneSide()方法需要实现。于是继续,根据给定的两点判断其余点是否都在两点连线的同一侧。首先找到任意一个第三点,根据直线方程y=kx+b判断他在给定两点连线的哪一侧,再判断其余点是否都在同一侧,如果不是该点就不是我们要找的点。
/// <summary> /// 判断所有点是否在两点连线一侧 /// </summary> /// <param name="l1"></param> /// <param name="l2"></param> /// <returns></returns> private bool AtOneSide(Location l1, Location l2) { double k = (l1.Latitude - l2.Latitude) / (l1.Longitude - l2.Longitude); double b = l1.Latitude - l1.Longitude * (l1.Latitude - l2.Latitude) / (l1.Longitude - l2.Longitude); bool v = GetV(l1, l2, k, b); foreach (Location l in list) { if (!l.Equals(l1) && !l.Equals(l2)) { if ((l.Latitude > (k * l.Longitude + b)) != v) return false; } } return true; } /// <summary> /// 点集中随便找到第三点,确定他在两点连线的哪一侧 /// </summary> /// <param name="l1"></param> /// <param name="l2"></param> /// <param name="k"></param> /// <param name="b"></param> /// <returns></returns> private bool GetV(Location l1, Location l2, double k, double b) { foreach (Location l in list) { if (!l.Equals(l1) && !l.Equals(l2)) { return l.Latitude > (k * l.Longitude + b); } } return false;//这个地方是走不到的 }
到这里就全部完成了,里面用到了中学数学知识直线方程。
遗留问题:本次处理的全部坐标为国内坐标,没有跨半球的情况。如果需要处理跨半球坐标需要将本初子午线和赤道的焦点视为平面直角坐标的原点进行坐标象限转换。
最后贴出完整代码

namespace ns { class FindPolygon { private List<Location> list; /// <summary> /// 用点集list初始化对象 /// </summary> /// <param name="_list"></param> public FindPolygon(List<Location> _list) { list = _list; } /// <summary> /// 从最北边的点开始找下一个点,使其余所有点在这个点和下个点的连线的同一侧 /// </summary> /// <returns>外接多边形顶点集合</returns> public List<Location> GetPolygon() { List<Location> result = new List<Location>(); //最北边的点作为多边形第一个点 Location a = NorthernMost(); Location b = NorthernMost(); result.Add(b);//add第一个点 Location c = GetNextPoint(b, b);//第一次找点特殊处理 while (!NorthernMost().Equals(c)) { a = b; b = c;//交换abc三个点,继续找下一个点 result.Add(c); c = GetNextPoint(b, a); } return result; } /// <summary> /// 找到最北边的点为多边形的第一个点 /// </summary> /// <returns></returns> private Location NorthernMost() { Location lo = list[1]; foreach (var l in list) { if (l.Latitude > lo.Latitude) lo = l; } return lo; } /// <summary> /// 给定一个点获取另一个点,使得列表中其他点都在两点连线同一侧,但是找到的这个点不能是上一个点 /// </summary> /// <param name="_currentpoint">给定这个点</param> /// <param name="_lastpoint">上一个点</param> /// <returns></returns> private Location GetNextPoint(Location _currentpoint, Location _lastpoint) { foreach (Location l in list) { if (!_currentpoint.Equals(l)) { if (AtOneSide(_currentpoint, l) && !_lastpoint.Equals(l)) return l; } } return null; } /// <summary> /// 判断所有点是否在两点连线一侧 /// </summary> /// <param name="l1"></param> /// <param name="l2"></param> /// <returns></returns> private bool AtOneSide(Location l1, Location l2) { double k = (l1.Latitude - l2.Latitude) / (l1.Longitude - l2.Longitude); double b = l1.Latitude - l1.Longitude * (l1.Latitude - l2.Latitude) / (l1.Longitude - l2.Longitude); bool v = GetV(l1, l2, k, b); foreach (Location l in list) { if (!l.Equals(l1) && !l.Equals(l2)) { if ((l.Latitude > (k * l.Longitude + b)) != v) return false; } } return true; } /// <summary> /// 点集中随便找到第三点,确定他在两点连线的哪一侧 /// </summary> /// <param name="l1"></param> /// <param name="l2"></param> /// <param name="k"></param> /// <param name="b"></param> /// <returns></returns> private bool GetV(Location l1, Location l2, double k, double b) { foreach (Location l in list) { if (!l.Equals(l1) && !l.Equals(l2)) { return l.Latitude > (k * l.Longitude + b); } } return false;//这个地方是走不到的 } } class Location { private double longitude; private double latitude; public Location(double _longitude, double _latitude) { longitude = _longitude; latitude = _latitude; } /// <summary> /// 经度 /// </summary> public double Longitude { get { return longitude; } set { longitude = value; } } /// <summary> /// 纬度 /// </summary> public double Latitude { get { return latitude; } set { latitude = value; } } public static bool operator ==(Location lhs, Location rhs) { return lhs.Latitude == rhs.Latitude && lhs.Longitude == rhs.Longitude; } public static bool operator !=(Location lhs, Location rhs) { return !(lhs == rhs); } public override bool Equals(object obj) { if (obj == null) { return false; } if ((obj.GetType().Equals(this.GetType())) == false) { return false; } Location temp = null; temp = (Location)obj; return this.Latitude.Equals(temp.Latitude) && this.Longitude.Equals(temp.Longitude); } public override int GetHashCode() { return this.Latitude.GetHashCode() + this.Longitude.GetHashCode(); } } }