前言

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. 优化SqlServer--数据压缩

    数据压缩是对存储和性能优势的加强.减少数据库占用的磁盘空间量将减少整体数据文件存储空间,在一下几个方面增加吞吐量: 1.更好的I/O利用率,每个页面可以读写更多的数据. 2.更好的内存利用率,缓冲区可 ...

  2. 0007《SQL必知必会》笔记03-汇总与分组数据

    1.有些时候需要数据的汇总值,而不是数据本身,比如对某些数据求和.计数.求最大最小值.求平均值,因此就有了5个聚集函数:AVE().COUNT().MAX().MIN().SUM(): (1)求平均值 ...

  3. SQL Server调优系列基础篇(子查询运算总结)

    前言 前面我们的几篇文章介绍了一系列关于运算符的介绍,以及各个运算符的优化方式和技巧.其中涵盖:查看执行计划的方式.几种数据集常用的连接方式.联合运算符方式.并行运算符等一系列的我们常见的运算符.有兴 ...

  4. 烂泥:学习ubuntu远程桌面(一):配置远程桌面

    本文由秀依林枫提供友情赞助,首发于烂泥行天下 公司服务器目前安装的都是ubuntu 14.04系统,而且由于业务需要,需要使用到ubuntu的远程桌面功能.所以本篇文章都是围绕ubuntu的远程桌面来 ...

  5. java 生产者消费者问题 并发问题的解决

    引言 生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,如下图所示,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况: 生产者消费者图 ...

  6. 内部类访问的局部变量必须加final

    (1)内部类是外部类的一个成员,就像外部类的成员方法一样,所以内部类有权限访问外部类的所有成员,包括private的.  (2)内部类不能访问外部类方法中的局部变量,除非变量是final的(一般发生在 ...

  7. python 缩进语法,优缺点

    Python的语法比较简单——采用缩进方式 缩进有利有弊: 好处之一是强迫你写出格式化的代码,但没有规定缩进是几个空格还是Tab.按照约定俗成的管理,应该始终坚持使用4个空格的缩进. 其二是强迫你写出 ...

  8. python读取excel并制表输出

    源码如下: #!/usr/bin/python #coding=UTF-8 import xlrd import sys from texttable import Texttable def she ...

  9. PHP之图像处理

    PHP中提供了一些对图像进行编辑处理的函数,其中最为典型的应用为随机图形验证码.图片水印以及数据统计中饼状图和柱状图的生成等 PHP中有的图形函数可以直接使用,但多数需要在安装了GD2函数库后才能使用 ...

  10. Mecanim 动作复用示例

    Mecanim动作复用 资源包 四个动画文件 一个Controller 不同的模型 让模型都生成Avter,然后让多个模型重用一套动作 复用动作预览 动画状态机 资源地址 Assets Store地址 ...