前言

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. java.lang.OutOfMemoryError处理错误

    内存详解 原因: 常见的有以下几种: 1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据: 2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收: 3.代码中存在死循环或循环产生过多 ...

  2. 实现如下类之间的继承关系,并编写Music类来测试这些类。

    实现如下类之间的继承关系,并编写Music类来测试这些类. package com.hanqi.test; public class Instrument { //输出弹奏乐器 public void ...

  3. js中正则表达式的模式匹配

    参考Javascript权威指南(第6版)第10章 1.正则表达式的定义 正则表达式有两种定义方法,通常使用直接量方式. (1)直接量 var pattern = /\d$/; var pattern ...

  4. JavaScript中的函数表达式

    在JavaScript中,函数是个非常重要的对象,函数通常有三种表现形式:函数声明,函数表达式和函数构造器创建的函数. 本文中主要看看函数表达式及其相关的知识点. 函数表达式 首先,看看函数表达式的表 ...

  5. IE10、IE11 User-Agent 网站无法写入Cookie 问题[转]

    你是否遇到过当使用一个涉及到Cookie操作的网站或者管理系统时,IE 6.7.8.9下都跑的好好的,唯独到了IE10.11这些高版本浏览器就不行了?好吧,这个问题码农连续2天内遇到了2次.那么,我们 ...

  6. Codeforces Round #286 Div.1 A Mr. Kitayuta, the Treasure Hunter --DP

    题意:0~30000有30001个地方,每个地方有一个或多个金币,第一步走到了d,步长为d,以后走的步长可以是上次步长+1,-1或不变,走到某个地方可以收集那个地方的财富,现在问走出去(>300 ...

  7. Vijos1451圆环取数[环形DP|区间DP]

    背景 小K攒足了路费来到了教主所在的宫殿门前,但是当小K要进去的时候,却发现了要与教主守护者进行一个特殊的游戏,只有取到了最大值才能进去Orz教主…… 描述 守护者拿出被划分为n个格子的一个圆环,每个 ...

  8. java函数参数默认值

    java函数参数默认值 今天,需要设定java函数参数的默认值,发现按照其它语言中的方法行不通 java中似乎只能通过函数的重载来实现 函数参数默认代码

  9. StringBuffer和StringBuilder的区别

    StringBuffer和StringBuilder的区别 StringBuffer与StringBuilder就不一样了,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在 ...

  10. ubuntu在命令行新建用户后无法进入桌面的原因

    在命名行模式下 用useradd新建一个用户后 在图形界面输入密码无法登陆 这是因为未对新建的用户进行任何配置 用adduser命令新建用户即可进入桌面 下面说一下useradd 和 adduser的 ...