APP下载

二次开发在航点航迹图批量绘制中的应用

2018-08-22张良红彭振宇

物探化探计算技术 2018年4期
关键词:航点二次开发点位

张良红, 杨 豪, 黄 雯, 彭振宇

(浙江省地质调查院,杭州 311203)

0 引言

航点航迹图是物化探野外调查质量监控的重要资料,包括航迹和点位信息(点号、坐标、日期和时间),一般要求制作成A4版面[1-2],上部分为航点航迹图,下部分为点位信息。野外调查获得的航点航迹数据一般为GPX格式,为制作、输出可编辑且与地理底图匹配的航点航迹图及点位信息,通常可利用Mapsource[3-4]、Global Mapper[5-6]等软件及二次开发[7-8]将航点航迹数据投影转换为Excel及MapGIS文件格式,然后在MapGIS中输出航点航迹图,并在Excel下编辑和整理点位信息,这些方法比较繁琐,使用起来需要对坐标转换方法比较熟悉。

近几年,浙江各县市土地质量地球化学调查工作陆续开展,快速制作航点航迹图成为地质人员急需解决的问题。为了避免手工转换过程中每次打开软件时重复设置投影转换和图形参数,以及手工创建和编辑航点点位信息。笔者在前人研究基础上,利用VC基于MapGIS、Excel、MSXML二次开发,实现了航点航迹图的批量绘制并同时输出Excel格式点位信息,为编制航点航迹图册提供了便利。

1 系统设计

本系统为Visual C++6.0开发的MFC多文档应用程序,二次开发实现的功能模块主要包括存取投影及图形参数、读取GPX数据、坐标转换、生成点线文件、输出点位信息等部分,各模块之间的关系及系统工作流程如图1所示。

图1 系统工作流程Fig.1 The flow chart

2 模块介绍

2.1 主要数据结构

本系统操作的主要数据为航点和航迹数据,一条航迹包括多个航迹点,每个航点包括时间、位置、点名等信息。为了读取数据、坐标转换、图形绘制等的方便和航点航迹图输出成果的需要,系统设置航点和航迹数据结构,航点结构存放航点点名、时间、坐标等信息,所有测点组成一个数组,数组采用MFC提供的模板类CArray;航迹仅需包含航迹名称和航迹坐标信息,点名和时间信息不使用故不用存储。航点信息和航迹信息数据结构定义如下:

//航点信息

typedef struct

{

CString Name;//点名

CString Time;//时间

D_DOT xy;//坐标,MapGIS点线实体类型数据结构

}WAYPT;

typedef CArray CWptSet;

//航迹信息

typedef struct

{

CString Name;//航迹名称

CArray Track;//航迹坐标

}TRACK;

2.2 存取参数

为了避免每次打开软件时重复设置投影转换及图形参数,可以将参数保存起来,程序运行后先读取将其作为默认参数,为批量制作航点航迹图提供便利。参数可以存储在外部文件(如INI文件9-10])中,也可存储在程序自身文件内。为了减少文件数量和交流的方便,本程序将参数数据附加在可执行文件(PE)[11]尾部,附加数据偏移和大小可通过读取PE文件最后一个节表中的PointerToRawData和SizeOfRawData数值来确定[12-13],具体实现方法如编程1。

编程1:获取PE文件附加数据偏移和大小

//成功返回附加数据偏移,失败返回0

DWORD GetOverlaySize(HANDLE hFile, PLARGE_INTEGER lpOverlaySize)

{

IMAGE_DOS_HEADER DosHeader;

DWORD dwOffset = 0, dwRead;

if (lpOverlaySize != NULL) lpOverlaySize->QuadPart = 0;

SetFilePointer(hFile, 0, NULL, FILE_BEGIN);//移到文件头

ReadFile(hFile, &DosHeader, sizeof(DosHeader), &dwRead, NULL);

if (DosHeader.e_magic == IMAGE_DOS_SIGNATURE)//MZ

{

IMAGE_NT_HEADERS NtHeaders;

SetFilePointer(hFile, DosHeader.e_lfanew, NULL, FILE_BEGIN);

ReadFile(hFile, &NtHeaders, FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader), &dwRead, NULL);

if (NtHeaders.Signature == IMAGE_NT_SIGNATURE)//PE

{

WORD NumberOfSections = NtHeaders.FileHeader.NumberOfSections;

if (NumberOfSections >= 1)

{

DWORD dwLastSection = NtHeaders.FileHeader.SizeOfOptionalHeader + (NumberOfSections - 1) * sizeof(IMAGE_SECTION_HEADER);

IMAGE_SECTION_HEADER SectionHeader;

SetFilePointer(hFile, dwLastSection, NULL, FILE_CURRENT);

ReadFile(hFile, &SectionHeader, sizeof(SectionHeader), &dwRead, NULL);

if (dwRead == sizeof(SectionHeader))

{

dwOffset = SectionHeader.PointerToRawData + SectionHeader.SizeOfRawData;

if (lpOverlaySize != NULL)

{

*(LPDWORD)lpOverlaySize = GetFileSize(hFile, (LPDWORD)lpOverlaySize + 1);

lpOverlaySize->QuadPart -= dwOffset;

}

}

}

}

}

return dwOffset;

}

退出系统前,将投影转换及图形参数写入PE文件,因读写权限问题不能直接将参数写入运行中的PE文件,可先将程序改名备份再将数据写入复制的文件,或等待程序完全退出再下入数据。

2.3 读取GPX文件

GPX是GPS数据交换格式[14],因采用XML语法和格式描述航点、航线和航迹,故可以通过微软公司的MSXML组件[15]来解析GPX文件。

VC中使用MSXML6.0需要添加以下两行:

#import

using namespace MSXML2;

读取航点和航迹比较相似,利用getElementsByTagName("wpt")、getElementsByTagName("trk")方法可以取得所有航点或航迹,然后利用selectSingleNode("name")、selectSingleNode("time")方法获得航点或航迹的名称与时间,利用selectNodes("trkseg//trkpt")获得所有航迹点,最后利用getAttribute("lon")、getAttribute("lat")方法取得每个航点或航迹对应的经纬度坐标,具体实现方法如编程2。

编程2:读取航点和航迹文件

//CWptSet和CTrkSet数组由CArray模板类创建

BOOL ReadGPXFile(LPCTSTR szFileName, CWptSet &m_WptDot, CTrkSet &m_TrkDot)

{

MSXML2::IXMLDOMDocumentPtr docPtr;

m_WptDot.RemoveAll();

m_TrkDot.RemoveAll();

CoInitialize(NULL);

docPtr.CreateInstance("Msxml2.DOMDocument");

if (docPtr->load(szFileName))

{

//读取航点

MSXML2::IXMLDOMNodeListPtr pWptList = docPtr->getElementsByTagName("wpt");

m_WptDot.SetSize(pWptList->Getlength());

for(long nWpt = 0; nWpt < pWptList->Getlength(); nWpt++)

{

MSXML2::IXMLDOMElementPtr pWptNode = pWptList->Getitem(nWpt);

MSXML2::IXMLDOMNodePtr pWptName = pWptNode->selectSingleNode("name");

MSXML2::IXMLDOMNodePtr pWptTime = pWptNode->selectSingleNode("time");

_variant_t varLon = pWptNode->getAttribute("lon");

_variant_t varLat = pWptNode->getAttribute("lat");

m_WptDot[nWpt].Time = pWptTime != NULL ? (char*)pWptTime->text : "";

m_WptDot[nWpt].Time.Replace('T', ' ');

m_WptDot[nWpt].Time.Replace('Z', ' ');

m_WptDot[nWpt].Name = pWptName != NULL ? (char*)pWptName->text : "";

m_WptDot[nWpt].xy.x = varLon.vt != VT_NULL ? (double)varLon : 0;

m_WptDot[nWpt].xy.y = varLat.vt != VT_NULL ? (double)varLat : 0;

}

//读取航迹

MSXML2::IXMLDOMNodeListPtr pTrkList = docPtr->getElementsByTagName("trk");

m_TrkDot.SetSize(pTrkList->Getlength());

for(long nTrk = 0; nTrk < pTrkList->Getlength(); nTrk++)

{

MSXML2::IXMLDOMNodePtr pTrkNode = pTrkList->Getitem(nTrk);

MSXML2::IXMLDOMNodePtr pTrkName = pTrkNode->selectSingleNode("name");

MSXML2::IXMLDOMNodeListPtr pTptList = pTrkNode->selectNodes("trkseg//trkpt");

m_TrkDot[nTrk].Track.SetSize(pTptList->Getlength());

for (long nTpt = 0; nTpt < pTptList->Getlength(); nTpt++)

{

MSXML2::IXMLDOMElementPtr pTptNode = pTptList->Getitem(nTpt);

_variant_t varLon = pTptNode->getAttribute("lon");

_variant_t varLat = pTptNode->getAttribute("lat");

m_TrkDot[nTrk].Track[nTpt].x = varLon.vt != VT_NULL ? (double)varLon : 0;

m_TrkDot[nTrk].Track[nTpt].y = varLat.vt != VT_NULL ? (double)varLat : 0;

}

}

}

docPtr.Release();

CoUninitialize();

return m_WptDot.GetSize() > 0 || m_TrkDot.GetSize() > 0;

}

2.4 坐标转换

GPX文件中读取的经纬度坐标是基于WGS-84坐标系的,将其转换为国家坐标系(西安80或北京54),需要设置WGS-84至国家坐标系的转换参数,MapGIS软件转换参数设置窗口如图2所示,二次开发时也将显示该对话框来设置转换参数。该参数与GPS中设置的不同,在一定范围内可认为相互转换过程可逆,即将GPS中设置的参数乘以-1即可。

图2 坐标转换参数设置Fig.2 Parameter configuration of coordinate transformation

MapGIS二次开发中,坐标转换先要利用_MapToInPara和_MapToOutPara函数设置输入和输出参数,然后循环利用_PntCProjTrans函数转换所有坐标点。

2.5 创建图形文件

为创建可编辑且与地理底图参数一致的航点航迹图,采用MapGIS进行二次开发。MapGIS二次开发方式主要有API函数、MFC类库和组件开发三种方式[16-17],MFC类库方式是使用VC++开发MapGIS应用程序最快速有效的方法[18-19],笔者采用这种方式。建立图形文件前,先利用AHinst=_InitWorkArea(NULL)、MapPrj=_PRJCreateProject(AHinst)、_PRJSetName和MapPrj->Prj_Head.PrjMapParam创建工程和工作区,并设置工程文件名称(含文件路径)和地图参数,然后利用下面两行分别创建点和线文件:

short ail = _PRJCreateNewItem(MapPrj, LIN, strName, strPath, 0, &MapPrj->Prj_Head.PrjMapParam);

short aip = _PRJCreateNewItem(MapPrj, PNT, strName, strPath, 0, MapPrj->Prj_Head.PrjMapParam);

在此基础上,利用_AppendLin和_AppendPnt函数根据航点、航迹坐标便可完成点和线的绘制,绘制完后利用_SaveAll(AHinst)和_PRJSaveProject(MapPrj)保存文件和工程。

2.6 输出点位信息

为了方便在Word文档中插入表格形式的点位信息,本程序将点位信息输出为XLS格式。VC++中COM组件方式读写Excel文件[20]需要将Office(以2007版为例)相关的MSO.dll、VBE6EXT.OLB和EXCEL.EXE文件复制到程序所在文件夹并添加以下几行:

#import "MSO.dll"

#import "VBE6EXT.OLB"

#import "EXCEL.EXE" rename("RGB","ExcelRGB") rename("DialogBox","ExcelDialogBox")

using namespace Office;

using namespace Excel;

using namespace VBIDE;

然后,利用_ApplicationPtr、_WorkbookPtr、_WorksheetPtr、RangePtr等智能指针像调用API函数一样操作Excel对象,添加完所有点位信息后保存文件为XLS格式,具体实现如编程3。

编程3:输出采样点位信息

BOOL ExportXlsFile(LPCTSTR szFileName, CWptSet &m_WptDot)

{

static char *Title[] = {"序号", "点名", "东坐标", "北坐标", "时间"};

if (m_WptDot.GetSize() > 0)

{

_ApplicationPtr pExcelApp;

long nRow, nWpt;

CString str;

CoInitialize(NULL);

pExcelApp.CreateInstance("Excel.Application");

_WorkbookPtr pWorkbook = pExcelApp->Workbooks->Add();

_WorksheetPtr pWorkSheet = pWorkbook->ActiveSheet;

RangePtr pRange = pWorkSheet->Cells;

for (nRow = 1; nRow <= sizeof(Title) / sizeof(char*); nRow++)

{

pRange->Item[(long)1][nRow] = Title[nRow - 1];

}

for (nWpt = 0; nWpt < m_WptDot.GetSize(); nWpt++)

{

nRow = nWpt + 2;

pRange->Item[nRow][(long)1] = nWpt + (long)1;

pRange->Item[nRow][(long)2] = (LPCTSTR)m_WptDot[nWpt].Name;

str.Format("%.lf", m_WptDot[nWpt].xy.x);

pRange->Item[nRow][(long)3] = (LPCTSTR)str;

str.Format("%.lf", m_WptDot[nWpt].xy.y);

pRange->Item[nRow][(long)4] = (LPCTSTR)str;

pRange->Item[nRow][(long)5] = (LPCTSTR)m_WptDot[nWpt].Time;

}

pRange->HorizontalAlignment = (long)Excel::xlHAlignCenter;

pRange = (RangePtr)(pWorkSheet->Columns->Item[(long)5]);

pRange->NumberFormatLocal = L"yyyy/m/d hh:mm:ss";

pRange->AutoFit();

pWorkSheet->GetUsedRange()->GetBorders()->LineStyle = _variant_t((long)xlContinuous);

pExcelApp->put_DisplayAlerts(0, VARIANT_FALSE);

_variant_t xlfmt = (long)xlExcel8;

pWorkSheet->SaveAs(szFileName, atoi(pExcelApp->Version) >= 12 ? xlfmt : vtMissing);

pWorkbook->Close();

pExcelApp->Quit();

pExcelApp.Release();

CoUninitialize();

return TRUE;

}

return FALSE;

}

2.7 编辑和整饰航点航迹图

本系统根据GPX中的航点和航迹自动生成点文件和线,并根据点名自动标注航点。有时航点比较密集,或采样前好长一段时间便开始记录航迹,或完成采样工作后航迹记录仍在继续,则生成的标注可能压盖线或位置不合理,这时需要通过工具栏上的编辑工具移动标注或剪断部分多余航迹,使航点航迹图比较整洁和清晰,然后再输出JPG文件。

3 系统使用

程序界面如图3所示,与MapGIS的编辑子系统窗口类似,左侧为工程文件管理列表,右侧为图形编辑交互窗口,不同的是本程序能够同时打开多个编辑窗口。用户可通过新建工程、导入GPX数据来手工完成航点航迹图的制作,也可一次打开多个GPX文件或拖放多个GPX文件到窗口完成多个航点航迹图的自动制作。手工的方式虽然不够方便,但新建工程后可临时修改地图参数。生成图形和工程文件后,可通过工具栏上的快捷菜单对航点航迹图进行修改、编辑和整饰,然后输出JPEG图像。

图3 程序运行界面Fig.3 Program running interface

使用程序时,首先需设置目录环境,尤其是系统库目录,它关系到点和线参数;然后设置地图参数,如果地图参数中椭球不是WGS84则必须设置转换参数,设置方法见图2,图中DX、DY和DZ为手持GPS中的相应参数;最后设置默认线参数和点参数。完成设置后关闭程序将保存默认地图参数、默认线和点参数,重新打开程序后将自动读取默认参数。设置参数后,将GPX文件拖放至窗口内或通过菜单或工具栏的批处理打开多个GPX文件后,将自动生成工程文件、点线文件及相应航点信息文件。需要注意的是输出的时间信息为格林尼治时间而非本地时间,与《多目标区域地球化学调查规范(1:250000)》(DZ/T 0258-2014)[1]、《地球化学普查规范》(1:50000)》(DZ/T 0011-2015)[2]等要求的一致,该时间加8 h后即为本地时间(北京时间)。

4 应用实例

每天工作后,将GPS中存储的航点和航迹数据导出为GPX格式,利用本系统生成航点航迹图和点位信息XLS文件,然后将设计的点文件添加到工程中,检查每个测点的编号(卡片、样品袋、XLS文件)和位置信息(卡片、XLS文件)是否一致,发现错误及时整改并形成日常检查记录。确认无误后保存航点航迹图和点位信息XLS文件,编辑整饰后的航点航迹图输出JPG格式。最后在Word中添加航点航迹图和点位信息表格,制作成A4版面,上部分为航点航迹图,下部分为点位信息,样式如图4所示。每个调查小组每日一张航点航迹图,野外调查完成后装订成册。

图4 航点航迹图Fig.4 Waypoints tracking map

5 结论

2016年临安、衢州等地土地质量地球化学调查中,利用本程序完成了航点航迹图的制作,取得以下主要认识和结论:

1)利用VC基于MapGIS、Excel、MSXML进行了二次开发,界面友好、使用方便、快捷、可操作性强,为后续项目的开展带来便利。

2)相比项目开展前期手工利用Global Mapper、MapGIS、MapSource等软件来生成航点航迹图和航点信息表,多种组件二次开发的有机结合明显降低了劳动强度,有效地提高了工作效率。

3)MFC类库方式开发MapGIS应用程序具有方便快速的特点,其图形信息处理功能可在物化探野外调查质量监控中发挥重要作用。

猜你喜欢

航点二次开发点位
台商急盼两岸大规模复航
基于结构光视觉的钻孔点位法矢检测技术研究
西门子Operate高级编程的旋转坐标系二次开发
浅谈Mastercam后处理器的二次开发
基于网格的航道模式提取算法研究∗
机器人点位控制速度规划算法选择策略
大盘仍在强烈下跌趋势中
西门子Easy Screen对倒棱机床界面二次开发
基于空间网格的机器人工作点位姿标定方法
ANSYS Workbench二次开发在汽车稳定杆CAE分析中的应用