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


最初的想法
一开始想(XMin,YMin)应该是多边形的左下角,(XMax,YMin)应该是右下角,对应的找到4个顶点转换成矩形应该会好做一点吧。但是GPS测量出来的数据可不是都是垂直坐标轴的矩形,有的是斜着的,这样就找不到XMin和YMin了,而且如果是不规则的多边形怎么办,硬要转成矩形的话那样误差很大啊。
正统的解法
根据网上搜索到的资料显示,这个问题属于计算几何这门学科解决的问题。然后涉及了很多向量啊,向量的叉积啊,还有各种几何知识,如何在2-3天之内补习这些知识然后写出代码来,看起来是个很艰苦的过程。
- 求点集的凹包,目的是减少多边形的点,一个GPS测量数据包含了少则500,多则3000多个连续的点集,所以要通过凹包来减少点集。
- 求两个凹包多边形的交集Mix
- 求交集Mix多边形的面积
- 比较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);
所以最后我们的步骤就是
- 根据GPS点集创建一个多边形
- 再创建第二个多边形
- 通过CombineRgn函数合并两个多边形,返回成功就表示有交集,返回失败就是无交集
- 有交集的情况下,再通过GetRegionData获取交集和第一个多边形的信息
- 计算GetRegionData里拆解出来的若干矩形的面积,长*宽
- 比较交集的面积和第一个多边形面积的比值
整个过程看起来很简单,这里我上一些代码,把其中比较难的部分解释一下。
创建一个多边形
先引用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围栏两个多边形相交问题的奇葩解法的更多相关文章
- hdu3060Area2(任意多边形相交面积)
链接 多边形的面积求解是通过选取一个点(通常为原点或者多边形的第一个点)和其它边组成的三角形的有向面积. 对于两个多边形的相交面积就可以通过把多边形分解为三角形,求出三角形的有向面积递加.三角形为凸多 ...
- Inheritance - SGU 129(线段与多边形相交的长度)
题目大意:给一个凸多边形(点不是按顺序给的),然后计算给出的线段在这个凸多边形里面的长度,如果在边界不计算. 分析:WA2..WA3...WA4..WA11...WA的无话可说,总之细节一定考虑清楚, ...
- Geometric Shapes (poj3449多边形相交)
题意:给你一些多边形的点,判断每个多边形和那些多边形相交,编号按照字典序输出 思路:枚举每个多边形的每条边看是否相交,这里的相交是包括端点的,关键是给你正方形不相邻两个点求另外两个点怎么求,长方形给你 ...
- 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 ...
- hdu 5130(2014广州 圆与多边形相交模板)
题意:一个很多个点p构成的多边形,pb <= pa * k时p所占区域与多边形相交面积 设p(x,y), (x - xb)^2+(y - yb)^2 / (x - xa)^2+(y ...
- dtIntersectSegmentPoly2D 2D上的线段与多边形相交计算 产生结果:是否相交,线段跨越的开始和结束百分比,相交的边
dtIntersectSegmentPoly2D(startPos, endPos, verts, nv, tmin, tmax, segMin, segMax): http://geomalgori ...
- hdu 5120(求两个圆环相交的面积 2014北京现场赛 I题)
两个圆环的内外径相同 给出内外径 和 两个圆心 求两个圆环相交的面积 画下图可以知道 就是两个大圆交-2*小圆与大圆交+2小圆交 Sample Input22 30 00 02 30 05 0 Sam ...
- POJ 2546 Circular Area(两个圆相交的面积)
题目链接 题意 : 给你两个圆的半径和圆心,让你求两个圆相交的面积大小. 思路 : 分三种情况讨论 假设半径小的圆为c1,半径大的圆为c2. c1的半径r1,圆心坐标(x1,y1).c2的半径r2,圆 ...
- 如何判断单链表是否存在环 & 判断两链表是否相交
给定一个单链表,只给出头指针h: 1.如何判断是否存在环? 2.如何知道环的长度? 3.如何找出环的连接点在哪里? 4.带环链表的长度是多少? 解法: 1.对于问题1,使用追赶的方法,设定两个指针sl ...
随机推荐
- Java并发之ThreadPoolExecutor 线程执行服务
package com.thread.test.thread; import java.util.concurrent.ExecutorService; import java.util.concur ...
- spring 依赖注入(IOC DI)
依赖注入(IOC DI) 依赖注入的两种方式: 1. set注入 Spring要求使用set注入方式的时候,Bean需要提供一个无参数的构造方法.并提供一个属性的setter方法.例如: packag ...
- mysql-2 mysql客户端
mysql 官方客户端 MySQL-Workbench 下载链接http://dev.mysql.com/downloads/workbench/ 具体安装步骤就不写了,直接一直下一步就可以了. 下 ...
- Oracle安装前用户信息设置
如果是重复安装,首先需要清除已经存在的软件安装记录: rm -fr /usr/local/bin/*oraenv rm -fr /usr/local/bin/dbhome rm -fr /usr/tm ...
- 设计模式C#实现(十一)——组合模式
意图 0 适用性 1 结构 2 实现 3 效果 4 意图 将对象组合成树形结构以表示“部分-整体”的层次结构.Composite使得用户对单个对象和组合对象的使用具有一致性. 适用性 你想表示对象的部 ...
- keil 怎样新建工程,编写代码?
打开keil uversion 4 新建工程 新建的工程名字,点击保存. 选择 cpu 单片机芯片 Atmel ----> AT89C51 不用将汇编代码加入工程,选择 “否” 新建文件,注意这 ...
- 深入理解TCP(二)
上一篇http://www.cnblogs.com/whc-uestc/p/4715334.html中已经讲到TCP跟踪一个拥塞窗口来(cwnd)提供拥塞控制服务,通过调节cwnd值以控制发送速率.那 ...
- 比较TFS与SVN,你必须知道的10点区别
相比SVN,对于TFS的优点我有以下几点看法,供大家参考: 1. 总体比较: TFS是一个应用软件生命周期管理(ALM)软件,是一个软件研发平台产品,其功能覆盖了软件研发过程中的所有环节(包括源代 ...
- OpenStack 企业私有云的若干需求(7):电信行业解决方案 NFV
自动扩展(Auto-scaling)支持 多租户和租户隔离 (multi-tenancy and tenancy isolation) 混合云(Hybrid cloud)支持 主流硬件支持.云快速交付 ...
- css3选择器(一)
直接开始正文. 一.css3同级元素通用选择器[update20161228] 选择器:E~F 匹配任何在E元素之后的同级F元素 Note:E~F选择器选中的是E元素后面同级元素中的全部F元素. 例: ...