前言

GPS测量仪测量的产地面积,然后提交到系统中,系统需要校验这块产地和其他产地是否有重叠,重叠超过10%就要提出警告这块产地已经被XXX登记入库了。GPS测量仪测量出来的数据是连续的经纬度坐标数据。现在的问题就转换成求一个一系列点围成的区域和其他区域是否存在交集。拿到这个需求我想应该很简单,网上应该有现成的代码吧。

先上成品

最初的想法

一开始想(XMin,YMin)应该是多边形的左下角,(XMax,YMin)应该是右下角,对应的找到4个顶点转换成矩形应该会好做一点吧。但是GPS测量出来的数据可不是都是垂直坐标轴的矩形,有的是斜着的,这样就找不到XMin和YMin了,而且如果是不规则的多边形怎么办,硬要转成矩形的话那样误差很大啊。

正统的解法

根据网上搜索到的资料显示,这个问题属于计算几何这门学科解决的问题。然后涉及了很多向量啊,向量的叉积啊,还有各种几何知识,如何在2-3天之内补习这些知识然后写出代码来,看起来是个很艰苦的过程。

  1. 求点集的凹包,目的是减少多边形的点,一个GPS测量数据包含了少则500,多则3000多个连续的点集,所以要通过凹包来减少点集。
  2. 求两个凹包多边形的交集Mix
  3. 求交集Mix多边形的面积
  4. 比较Mix多边形的面积和原始凹包多边形面积的比值

以上步骤最复杂的应该是求凹包和求交集了,原谅我学到凸包多边形就已经放弃了。

奇葩的解法

为何叫奇葩的解法, 其实是为了赚一波眼球而已,我最后选择的解法是通过gdi32.dll提供的windowsAPI来完成的,下面介绍下用到的几个API函数

根据点集创建一个多边形

IntPtr CreatePolygonRgn(Point[] lpPoint, int nCount, int nPolyFillMode);

  

合并两个多边形,可以是OR/AND/XOR,这里用到的是AND

int CombineRgn(IntPtr dest, IntPtr src1, IntPtr src2, int flags);

  

获取一个多边形的详细数据,拆解为若干个矩形

int GetRegionData(HandleRef hRgn, int size, IntPtr lpRgnData);

  

所以最后我们的步骤就是

  1. 根据GPS点集创建一个多边形
  2. 再创建第二个多边形
  3. 通过CombineRgn函数合并两个多边形,返回成功就表示有交集,返回失败就是无交集
  4. 有交集的情况下,再通过GetRegionData获取交集和第一个多边形的信息
  5. 计算GetRegionData里拆解出来的若干矩形的面积,长*宽
  6. 比较交集的面积和第一个多边形面积的比值

整个过程看起来很简单,这里我上一些代码,把其中比较难的部分解释一下。

创建一个多边形

先引用dll

		[DllImport("gdi32")]
private static extern IntPtr CreatePolygonRgn(Point[] lpPoint, int nCount, int nPolyFillMode);

  

代码中Point是System.Drawing空间下的

X和Y*1000000是因为GPS信息是118.12334232这样的数据,如果直接取int的话就都变成118了,精度不够。

代码中IntPtr是句柄,windowsAPI编程中都是提供句柄,类似内存指针的玩意。

Point[] poin = new Point[gpsList.Count()];

for (int i = ; i < gpsList.Count(); i++)
{
string[] xy = gpsList[i].Split(',');
double x = ConvertHelp.obj2Double(xy[], );
double y = ConvertHelp.obj2Double(xy[], );
poin[i].X = (int)(x * );
poin[i].Y = (int)(y * );
} IntPtr orginRgn = IntPtr.Zero;
orginRgn = CreatePolygonRgn(poin, poin.Count(), );

比较两个多边形

先引用dll

    /// <summary>
/*
* CombineRgn(
p1: HRGN; {合成后的区域}
p2, p3: HRGN; {两个原始区域}
p4: Integer {合并选项; 见下表}
): Integer; {有四种可能的返回值} //合并选项:
RGN_AND = 1;
RGN_OR = 2;
RGN_XOR = 3;
RGN_DIFF = 4;
RGN_COPY = 5; {复制第一个区域} //返回值:
ERROR = 0; {错误}
NULLREGION = 1; {空区域}
SIMPLEREGION = 2; {单矩形区域}
COMPLEXREGION = 3; {多矩形区域}
*/
/// </summary>
/// <param name="dest"></param>
/// <param name="src1"></param>
/// <param name="src2"></param>
/// <param name="flags"></param>
/// <returns></returns>
[DllImport("gdi32.dll", CharSet = CharSet.Auto)]
public static extern int CombineRgn(IntPtr dest, IntPtr src1, IntPtr src2, int flags);

使用起来就很简单,提供3个参数,第一个参数是合并后返回的句柄,第2个参数是多边形1号,第3个参数是多边形2号,最后一个flag参数是合并选项,1是and,2是or根据情况选用,这里选用And所以是1

返回结果0表示错误也就是没有交集,1表示空区域即无交集,2和3都表示有交集存在。

int nMix = CombineRgn(nextRgn, orginRgn, nextRgn, );
if (nMix != && nMix != )
{
//有交集
}

获取交集的数据

计算交集的面积,其实就是如何根据句柄读取内存里的数据,因为网上大多数都是C++的写法,很少能找到。Net的写法,所以这个部分占用了我一下午时间,包括走了一些弯路 ,最后通过google才找到了正解。

先引用dll,根据API返回的数据结构建立对应的结构

        public struct RGNDATAHEADER
{
public int dwSize;
public int iType;
public int nCount;
public int nRgnSize;
public RECT rcBound;
} public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
/// <summary>
/// 获取数据参考:http://www.pinvoke.net/default.aspx/gdi32/GetRegionData.html
/// 数据结构参考:http://www.cnblogs.com/del/archive/2008/05/20/1203446.html
/// </summary>
/// <param name="hRgn"></param>
/// <param name="size"></param>
/// <param name="lpRgnData"></param>
/// <returns></returns>
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int GetRegionData(HandleRef hRgn, int size, IntPtr lpRgnData);

下面的代码只讲一下GetRegionData这个API的调用的特殊之处,首先他要调用2次才能正确获取到数据。

第一次调用如下GetRegionData(hr, 0, IntPtr.Zero),传递一个空句柄,此时会返回一个int的值,告诉你需要准备一个多大的内存区域。

然后要申请一个内存区域准备去接值。IntPtr bytes = Marshal.AllocCoTaskMem(dataSize);就是准备一个特定大小的内存区域的句柄。

第二次调用GetRegionData(hr, dataSize, bytes)的时候就能把我们想要的数据填充到bytes这个句柄指向的内存区域了。

接下来的问题就是有了句柄如何取结构化的数据了,C#里支持指针操作,但是是unsafe的代码。最关键一句话在下面已经加了注释 了。

	const int RDH_RECTANGLES = 1;

        /// <summary>
/// 分割多边形,获取多边形内所有的矩形
/// </summary>
/// <param name="hRgn"></param>
/// <returns></returns>
public unsafe static RECT[] RectsFromRegion(IntPtr hRgn)
{
RECT[] rects = null;
var hr = new HandleRef(null, hRgn);
// First we call GetRegionData() with a null buffer.
// The return from this call should be the size of buffer
// we need to allocate in order to receive the data.
int dataSize = GetRegionData(hr, , IntPtr.Zero); if (dataSize != )
{
IntPtr bytes = IntPtr.Zero; // Allocate as much space as the GetRegionData call
// said was needed
bytes = Marshal.AllocCoTaskMem(dataSize); // Now, make the call again to actually get the data
int retValue = GetRegionData(hr, dataSize, bytes); // From here on out, we have the data in a buffer, and we
// just need to convert it into a form that is more useful
// Since pointers are used, this whole routine is 'unsafe'
// It's a small sacrifice to make in order to get this to work.
// [RBS] Added missing second pointer identifier
RGNDATAHEADER* header = (RGNDATAHEADER*)bytes; if (header->iType == RDH_RECTANGLES)
{
rects = new RECT[header->nCount]; // The rectangle data follows the header, so we offset the specified
// header size and start reading rectangles.
//获取偏移
int rectOffset = header->dwSize;
for (int i = ; i < header->nCount; i++)
{
// simple assignment from the buffer to our array of rectangles
// will give us what we want.
//首先把bytes转换成指针,得到bytes的地址,然后加上偏移,再转换为RECT类型的指针。
rects[i] = *((RECT*)((byte*)bytes + rectOffset + (Marshal.SizeOf(typeof(RECT)) * i)));
}
} } // Return the rectangles
return rects; }

计算面积

因为上面获取到的数据其实是一个RECT的数组,而RECT里面包含的是上下左右四个点的坐标信息,那么很显然我们得到的是一个矩形的数组,每次分解大概有2000多个矩形,不用管多少了,直接拿来用就是了

        /// <summary>
/// 计算多边形的面积
/// </summary>
/// <param name="rgn"></param>
/// <returns></returns>
public static int CalculateAreas(IntPtr rgn)
{
RECT[] rectData = RectsFromRegion(rgn);
int ret = ;
foreach (var rect in rectData)
{
int areas = (rect.Bottom - rect.Top) * (rect.Right - rect.Left);
if (areas < )
areas = areas * -;
ret += areas;
//Console.WriteLine("{0},{1},{2},{3},{4}", rect.Top, rect.Left, rect.Right, rect.Bottom, areas);
}
return ret;
}

总结

这次这个需求一开始以为不复杂,网上应该有现成的代码,实际上搜索后发现涉及的计算几何的知识对几何和算法的要求特别高,无奈几何知识都还给老师了,补习的话短短2-3天应该是来不及了。百度的能力毕竟有限,有的时候google能提供更大的帮助。通过另辟蹊径借用windowsAPI解决了这个问题,同时了解了C#在指针操作上的知识。

GPS围栏两个多边形相交问题的奇葩解法的更多相关文章

  1. hdu3060Area2(任意多边形相交面积)

    链接 多边形的面积求解是通过选取一个点(通常为原点或者多边形的第一个点)和其它边组成的三角形的有向面积. 对于两个多边形的相交面积就可以通过把多边形分解为三角形,求出三角形的有向面积递加.三角形为凸多 ...

  2. Inheritance - SGU 129(线段与多边形相交的长度)

    题目大意:给一个凸多边形(点不是按顺序给的),然后计算给出的线段在这个凸多边形里面的长度,如果在边界不计算. 分析:WA2..WA3...WA4..WA11...WA的无话可说,总之细节一定考虑清楚, ...

  3. Geometric Shapes (poj3449多边形相交)

    题意:给你一些多边形的点,判断每个多边形和那些多边形相交,编号按照字典序输出 思路:枚举每个多边形的每条边看是否相交,这里的相交是包括端点的,关键是给你正方形不相邻两个点求另外两个点怎么求,长方形给你 ...

  4. You can Solve a Geometry Problem too(判断两线段是否相交)

    You can Solve a Geometry Problem too Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/3 ...

  5. hdu 5130(2014广州 圆与多边形相交模板)

    题意:一个很多个点p构成的多边形,pb <= pa * k时p所占区域与多边形相交面积 设p(x,y),       (x - xb)^2+(y - yb)^2 / (x - xa)^2+(y ...

  6. dtIntersectSegmentPoly2D 2D上的线段与多边形相交计算 产生结果:是否相交,线段跨越的开始和结束百分比,相交的边

    dtIntersectSegmentPoly2D(startPos, endPos, verts, nv, tmin, tmax, segMin, segMax): http://geomalgori ...

  7. hdu 5120(求两个圆环相交的面积 2014北京现场赛 I题)

    两个圆环的内外径相同 给出内外径 和 两个圆心 求两个圆环相交的面积 画下图可以知道 就是两个大圆交-2*小圆与大圆交+2小圆交 Sample Input22 30 00 02 30 05 0 Sam ...

  8. POJ 2546 Circular Area(两个圆相交的面积)

    题目链接 题意 : 给你两个圆的半径和圆心,让你求两个圆相交的面积大小. 思路 : 分三种情况讨论 假设半径小的圆为c1,半径大的圆为c2. c1的半径r1,圆心坐标(x1,y1).c2的半径r2,圆 ...

  9. 如何判断单链表是否存在环 & 判断两链表是否相交

    给定一个单链表,只给出头指针h: 1.如何判断是否存在环? 2.如何知道环的长度? 3.如何找出环的连接点在哪里? 4.带环链表的长度是多少? 解法: 1.对于问题1,使用追赶的方法,设定两个指针sl ...

随机推荐

  1. UNABLE TO PURGE A RECORD(二)

    上一篇文章说明了bug出现的原因和原理分析,要修复bug似乎已经水到渠成了,但远没有这么简单,只因为“并发”.要修复问题,首先要做的第一件事情是稳定的复现问题.由于数据库系统是一个并发系统,并且这个b ...

  2. mysql源码解读之配置文件

    要研究mysql,最好的资源莫过于源码了,所以本人打算通过调试源码的方式来深入理解mysql的点点滴滴.搭建mysql调试环境很简单,从官方下载mysql源码,利用cmake工具生成工程即可.为了方便 ...

  3. 简单看看这两个类 String和StringBuilder

    我记得以前在园子里面讨论这两个类的文章有很多很多,并且还拿出了很多的测试报告,在什么情况下,谁比谁快,在什么情况下,该用谁 不该用谁等等这些,我这里就不比较了,我就简单看看他们里面的内部实现,那就先看 ...

  4. SQL Server:统计数据库中每张表的大小

    1. 统计数据库中每张表的大小 1.1 首先执行下面的命令 exec sp_MSforeachtable @command1="sp_spaceused '?'"; 1.2 检测当 ...

  5. H264解码学习-2015.04.16

    今天看了不少,却感觉收获寥寥. 1.H264相关知识 因为RTP协议发过来的数据已经经过了H264编码,所以这边需要解码.补充一下H264的相关知识. 与以往的视频压缩标准相比,H.264 视频压缩标 ...

  6. MySQL创建数据库和表的Demo

    Demo: 创建数据库的语法 1.基本语法 create database tour character set gbk; use tour; 无主键自增长的 create table EMB_T_E ...

  7. Linux磁盘管理之设备文件详解04

    Linux一切接文件,除了普通文件和目录文件,还包括一些其它的特殊文件:块设备文件.字符设备文件.套接字文件.链接文件等.今天这里主要说一下常见的块设备文件和字符设备文件,这2类是最常见的设备文件类. ...

  8. docker-7 docker在阿里云的使用

    在传统模式中,开发团队在开发环境中完成软件开发,自己做了一遍单元测试, 测试通过,ᨀ交到代码版本管理库.运维把应用部署到测 试环境, QA 进行测试,没问题后通知部署人员发布到生产环境. 在上述过程中 ...

  9. x01.Lab.OpenCV: 计算机视觉

    横看成岭侧成峰,计算视觉大不同.观看的角度不同,成像自然不同,这对计算机视觉来说,是个大麻烦.但计算机视觉应用如此广泛,却又有不得不研究的理由.指纹机大家都用过吧,这不过是冰山之一角.产品检测,机器人 ...

  10. php 连续留存与留存人数计算

    for($i = 0;$i <= $interval;$i++) { $res = $model->turnround($today,$tomorrow,$flag); $temp = a ...