Webgis 打印实现技术细节
2006-12-22 15:15
281 查看
[align=center]Webgis 打印实现技术细节[/align]
[align=center]——兼论如何实现打印预览[/align]
[align=center]褫其华衮,示人本相系列之三[/align]
[align=center] [/align]
[align=center]cheungmine[/align]
在我的上一篇文章《Webgis 打印实现原理——褫其华衮,示人本相系列之二》(见:http://blog.csdn.net/cheungmine/archive/2006/09/18/1238708.aspx)里,提到了Webgis的打印原理。其实,这个原理不仅仅适用于Webgis的打印,还适合实现打印和打印预览。打印地图的过程其实就是在打印DC上把地图绘制一遍的过程,和显示地图不同的是,需要计算比例尺,进行坐标换算。
一、准备知识
为了理解这个过程,我先假设一下我用到的组件,一个基本的绘图系统需要下面2个元件:
1)地图绘制组件(MapDraw)——用来在给定的DC平面上画图。必须使用DC设备坐标来画图。如:::LineTo(hDC, vx, vy) 中必须使用DC坐标,对于屏幕DC,(vx, vy)就是象素;对于打印机DC,就是点。
2)坐标变换组件(Viewport)——用来把地图坐标变换到DC平面的坐标,和它们的反变换。
这样,显示和打印的过程就是:
..
地图数据—>MapDraw载入—>Viewport变换到DC坐标—>MapDraw绘制DC
(关于上面2个组件的实现已经超出了本文的范围。我仅讲述如何实现打印。)
设给定要打印的数据范围:rcData(Xmin,Ymin,Xmax,Ymax)。给定打印比例尺:fScale(如=1/500)。假设我们已经取得DC(这个DC可以是屏幕DC,也可以是打印DC,或图象DC)。任何一个DC都有属性:Width(宽)、Height(高或长Length)、xDPI(水平方向分辨率)、yDPI(竖直方向分辨率)。
其中,rcData、Width和Height这些参数输入到Viewport组件,就可以实现下面的变换:
..
地图坐标DP(dx, dy) —> DC坐标VP(vx, vy)
DC坐标VP(vx, vy) —> 地图坐标DP(dx, dy)
Viewport组件有DP2VP和VP2DP函数实现这对变换。可以看出,Viewport是这里的核心。实现Viewport并不难,如果有必要,我会在以后的文章中专门讲这个变换类。其余的细节,比如页边距、对齐方式等等就是细节。
可以把DC看作是一张纸,它的坐标的(0,0)点就在纸的左上角,你在这张纸上绘图所用到的尺子(度量),它的刻度不是m、cm、mm或inch,而是点(dot)。你只有用这个dot来在这张纸上作业。至于如何换算到实际的度量单位,比如毫米(mm),那就全靠纸的属性决定:xDPI和yDPI,它们分别表示2个方向上的DC分辨率(DPI——dots per inch,每英寸点数)。按比例打印地图需要注意的问题是,纸张的大小限制了每页可打印的数据范围,所以必须计算出页面的数据范围,打印每页的时候都要做Viewport变换。
我的经验是,绝对不要使用Windows GDI提供的坐标映射API。我们只要使用MM_TEXT映射(这是默认的),其余的事情交给Viewport来做。再重复一次,绝对不要使用Windows GDI提供的坐标映射API来做坐标变换,那只会把你搞糊涂。起码我的聪明程度还不足以架驭这些API来做复杂的事情。
二、打印预览
实现打印预览比实现打印本身还复杂。如果你有能力花半年时间写一个类似Mircosoft Office Document Image Writer(MODI)的东西,恭喜你,你实现了打印预览。其实就是MODI本身还是有问题,比如最大分辩率是300dpi(太小了,不是么)。虽然可以通过安装虚拟打印机的方式,来预览打印结果,但是这个模式的前提是使用了第3方的软件,而通常那是要付费的。本文所讲的打印预览不依赖于需要付费的方式,而且能够实现Webgis的打印。原理在前面的文章中已有论述。实现的具体步骤是:
..
地图数据—>MapDraw载入—>Viewport变换到DC坐标
..
—>MapDraw绘制到图象DC—>Tiff2Pdf生成PDF文件
..
可以看到,这里使用的DC是图象DC。稍后可以看到如何使用它。下面我把实现打印预览的伪代码写出来,整个过程就一目了然拉:
//
// 下面结构定义了打印页面的属性
//
struct PRINT_PDFPAGE
{
// 页面尺寸(单位:象素),页面是纸张去掉页边距的剩余部分
SIZE tifPage;
// 下面的属性可以构造出坐标变换类Viewport:
RECTF rcPageData; // 页面数据范围,如:
// (20532.123, 1234.678, 34562.112, 2316.870)
RECT rcPageView; // 页面数据范围对应的DC范围,如:
// (0, 0, 2048, 2048)
};
//
// 下面的结构定义了绘制结构
//
struct DRAW_INFO
{
HDC hDC; // 页面图象DC
COLORREF crBkgnd; // 底色:=CLR_INVALID (-1)表示无底色,
// 0x0 ~ 0xffffff表示有底色
// 因为PDF是由图象生成的,而图象默认是黑色背景,所以要
// 填充图象的底色
Viewport vwp; // 当前的坐标变换类
};
///////////////////////////////////////////////////////////////////////////////
// 下面的方法实现了打印预览PDF
// 使用CxImage创建多页TIFF, 然后使用tiff2pdf转换成pdf
// CxImage和CxImageTIF是cximage提供的类,参见:http://www.xdp.it/
// tiff2pdf是LIBTIFF提供的工具,需要改成一个函数
///////////////////////////////////////////////////////////////////////////////
void PrintPDF( BSTR bstrPdfFileName, // PDF文件全路径名
int nMapUnit, // 地图单位
float fScale, // 打印比例尺
RECT rcMargin, // 页边距
short pdfDPI, // 打印分辨率(dpi), x和y方向一致
FLOAT inPaperWidth, // 纸张宽(单位:inch)
FLOAT inPaperLength, // 纸张高(单位:inch)
COLORREF crBkgnd // 背景色
)
{
// 临时多页TIF文件名
CComBSTR bstrMultiTifs(bstrPdfFileName);
bstrMultiTifs += ".tif";
// 页面数组
vector<PRINT_PDFPAGE> arrPdfPages;
// 计算打印页面数组。CalcPDFPages方法的实现就是计算每个页面的数据范围
// CalcPDFPages的原型如:
// CalcPDFPages(int, FLOAT, RECT, FLOAT, FLOAT, FLOAT,
// vector<PRINT_PDFPAGE>&);
CalcPDFPages(nMapUnit, fScale, rcMargin, pdfDPI,
inPaperWidth,
inPaperLength,
arrPdfPages);
int nPages = (int) arrPdfPages.size();
// 用于创建多页TIF的类CMultiTifs 。实现这个类很简单,你只要了解如何使用
// CxImage和CxImageTIF类就行。比如下面的代码创建了一个3页的TIF文件
// multipage.tif:
// CxImage *pimage[3];
// pimage[0]=&image1;
// pimage[1]=&image2;
// pimage[2]=&image3;
// FILE* hFile;
// hFile = fopen("multipage.tif","w+b");
// CxImageTIF multiimage;
// multiimage.Encode(hFile,pimage,3);
// fclose(hFile);
// CMultiTifs类要自己实现
CMultiTifs mtifs(bstrMultiTifs, nPages);
// 打印每一页
for (int iPage=0; iPage<nPages; iPage++)
{
// 当前页结构
PRINT_PDFPAGE pdfPage = arrPdfPages[iPage];
// CImage是ATL71提供的C++类。我觉得这个类的唯一好处就是提供了GetDC()
// 这个东西。这个类存在BUG,这也是我不直接使用它的DC而是使用MemDC的原
// 因。我不会自己创建符合我需要的DC,只好请CImage代劳拉。
CImage imgDC; // 仅仅通过imgDC取得位图DC
BOOL bRes = imgDC.Create(16, 16, 32); // 创建一个小的位图
ATLASSERT(bRes);
// 注意:如果直接使用imgDC的DC,当更换打印DPI时,可能发现只打印一小部
// 分。我觉得它的DC,多次生成有问题。所以使用了HBITMAP转了一下。
HDC hdcImg = imgDC.GetDC(); // 取得位图DC
HDC hMemDC = ::CreateCompatibleDC(hdcImg);
HBITMAP hbmp = ::CreateCompatibleBitmap(hdcImg,
pdfPage.tifPage.cx,
pdfPage.tifPage.cy);
ATLASSERT(hbmp);
// 保存DC...
int nSavedDC = ::SaveDC(hMemDC);
::SelectObject(hMemDC, hbmp);
//
// 填充绘制结构
//
DRAW_INFO di;
di.hDC = hMemDC;
di.crBkgnd = crBkgnd;
// 生成坐标变换类
di.vwp.Init(pdfPage.rcPageView, pdfPage.rcPageData);
//
// 绘制页面
//
DrawMap(&di);
// 恢复DC
::RestoreDC(hMemDC, nSavedDC);
DeleteDC(hMemDC);
imgDC.ReleaseDC();
imgDC.Destroy();
// 使用CxImage重新打开文件
CxImage* pximg = new CxImage();
ATLASSERT(pximg);
bool brc = pximg->CreateFromHBITMAP((HBITMAP) hbmp);
DeleteObject(hbmp); // 使用完毕,删除之
ATLASSERT(brc);
// 添加图片
mtifs.AddPage(pximg);
}
// 创建多页tif
BOOL bRes = mtifs.Encode();
ATLASSERT(bRes);
// 转换TIF到PDF
// 下面的转换使用了开源的libtiff库v3.8.2。
// 下载自:http://www.remotesensing.org/libtiff/
// 你需要包装成自己的方法:tiff2pdf
tiff2pdf(bstrMultiTifs, bstrPdfFileName, pdfDPI, inPaperWidth, inPaperLength);
}
三、结束语
好拉,终于说的差不多了。一个小问题,知道如何计算页面图象的尺寸么?假设打印分辨率是300dpi,而纸张是8.27 x 11.69 inch(A4)大小。则页面图象就是:
宽:8.27*300 = 2481 象素
高:11.69*300 = 3507 象素
这样,你的纸张就是2481 x 3507 大小。你在这张纸上的所有度量都是参照这个大小来进行的。最后,你在把这个页面图象转换成PDF的时候,一定不要忘记告诉它你要的DPI(这里是300)啊!
既然有了PDF,就可以使用Adobe Reader来使用它拉。用Adobe Reader预览你的地图,难道不比自己写一个来的划算么?
我花了很长时间在编辑我的文章,最后显示出来还是不整齐.我没办法拉!而且无法加入图象!遗憾!!
[align=center]——兼论如何实现打印预览[/align]
[align=center]褫其华衮,示人本相系列之三[/align]
[align=center] [/align]
[align=center]cheungmine[/align]
在我的上一篇文章《Webgis 打印实现原理——褫其华衮,示人本相系列之二》(见:http://blog.csdn.net/cheungmine/archive/2006/09/18/1238708.aspx)里,提到了Webgis的打印原理。其实,这个原理不仅仅适用于Webgis的打印,还适合实现打印和打印预览。打印地图的过程其实就是在打印DC上把地图绘制一遍的过程,和显示地图不同的是,需要计算比例尺,进行坐标换算。
一、准备知识
为了理解这个过程,我先假设一下我用到的组件,一个基本的绘图系统需要下面2个元件:
1)地图绘制组件(MapDraw)——用来在给定的DC平面上画图。必须使用DC设备坐标来画图。如:::LineTo(hDC, vx, vy) 中必须使用DC坐标,对于屏幕DC,(vx, vy)就是象素;对于打印机DC,就是点。
2)坐标变换组件(Viewport)——用来把地图坐标变换到DC平面的坐标,和它们的反变换。
这样,显示和打印的过程就是:
..
地图数据—>MapDraw载入—>Viewport变换到DC坐标—>MapDraw绘制DC
(关于上面2个组件的实现已经超出了本文的范围。我仅讲述如何实现打印。)
设给定要打印的数据范围:rcData(Xmin,Ymin,Xmax,Ymax)。给定打印比例尺:fScale(如=1/500)。假设我们已经取得DC(这个DC可以是屏幕DC,也可以是打印DC,或图象DC)。任何一个DC都有属性:Width(宽)、Height(高或长Length)、xDPI(水平方向分辨率)、yDPI(竖直方向分辨率)。
其中,rcData、Width和Height这些参数输入到Viewport组件,就可以实现下面的变换:
..
地图坐标DP(dx, dy) —> DC坐标VP(vx, vy)
DC坐标VP(vx, vy) —> 地图坐标DP(dx, dy)
Viewport组件有DP2VP和VP2DP函数实现这对变换。可以看出,Viewport是这里的核心。实现Viewport并不难,如果有必要,我会在以后的文章中专门讲这个变换类。其余的细节,比如页边距、对齐方式等等就是细节。
可以把DC看作是一张纸,它的坐标的(0,0)点就在纸的左上角,你在这张纸上绘图所用到的尺子(度量),它的刻度不是m、cm、mm或inch,而是点(dot)。你只有用这个dot来在这张纸上作业。至于如何换算到实际的度量单位,比如毫米(mm),那就全靠纸的属性决定:xDPI和yDPI,它们分别表示2个方向上的DC分辨率(DPI——dots per inch,每英寸点数)。按比例打印地图需要注意的问题是,纸张的大小限制了每页可打印的数据范围,所以必须计算出页面的数据范围,打印每页的时候都要做Viewport变换。
我的经验是,绝对不要使用Windows GDI提供的坐标映射API。我们只要使用MM_TEXT映射(这是默认的),其余的事情交给Viewport来做。再重复一次,绝对不要使用Windows GDI提供的坐标映射API来做坐标变换,那只会把你搞糊涂。起码我的聪明程度还不足以架驭这些API来做复杂的事情。
二、打印预览
实现打印预览比实现打印本身还复杂。如果你有能力花半年时间写一个类似Mircosoft Office Document Image Writer(MODI)的东西,恭喜你,你实现了打印预览。其实就是MODI本身还是有问题,比如最大分辩率是300dpi(太小了,不是么)。虽然可以通过安装虚拟打印机的方式,来预览打印结果,但是这个模式的前提是使用了第3方的软件,而通常那是要付费的。本文所讲的打印预览不依赖于需要付费的方式,而且能够实现Webgis的打印。原理在前面的文章中已有论述。实现的具体步骤是:
..
地图数据—>MapDraw载入—>Viewport变换到DC坐标
..
—>MapDraw绘制到图象DC—>Tiff2Pdf生成PDF文件
..
可以看到,这里使用的DC是图象DC。稍后可以看到如何使用它。下面我把实现打印预览的伪代码写出来,整个过程就一目了然拉:
//
// 下面结构定义了打印页面的属性
//
struct PRINT_PDFPAGE
{
// 页面尺寸(单位:象素),页面是纸张去掉页边距的剩余部分
SIZE tifPage;
// 下面的属性可以构造出坐标变换类Viewport:
RECTF rcPageData; // 页面数据范围,如:
// (20532.123, 1234.678, 34562.112, 2316.870)
RECT rcPageView; // 页面数据范围对应的DC范围,如:
// (0, 0, 2048, 2048)
};
//
// 下面的结构定义了绘制结构
//
struct DRAW_INFO
{
HDC hDC; // 页面图象DC
COLORREF crBkgnd; // 底色:=CLR_INVALID (-1)表示无底色,
// 0x0 ~ 0xffffff表示有底色
// 因为PDF是由图象生成的,而图象默认是黑色背景,所以要
// 填充图象的底色
Viewport vwp; // 当前的坐标变换类
};
///////////////////////////////////////////////////////////////////////////////
// 下面的方法实现了打印预览PDF
// 使用CxImage创建多页TIFF, 然后使用tiff2pdf转换成pdf
// CxImage和CxImageTIF是cximage提供的类,参见:http://www.xdp.it/
// tiff2pdf是LIBTIFF提供的工具,需要改成一个函数
///////////////////////////////////////////////////////////////////////////////
void PrintPDF( BSTR bstrPdfFileName, // PDF文件全路径名
int nMapUnit, // 地图单位
float fScale, // 打印比例尺
RECT rcMargin, // 页边距
short pdfDPI, // 打印分辨率(dpi), x和y方向一致
FLOAT inPaperWidth, // 纸张宽(单位:inch)
FLOAT inPaperLength, // 纸张高(单位:inch)
COLORREF crBkgnd // 背景色
)
{
// 临时多页TIF文件名
CComBSTR bstrMultiTifs(bstrPdfFileName);
bstrMultiTifs += ".tif";
// 页面数组
vector<PRINT_PDFPAGE> arrPdfPages;
// 计算打印页面数组。CalcPDFPages方法的实现就是计算每个页面的数据范围
// CalcPDFPages的原型如:
// CalcPDFPages(int, FLOAT, RECT, FLOAT, FLOAT, FLOAT,
// vector<PRINT_PDFPAGE>&);
CalcPDFPages(nMapUnit, fScale, rcMargin, pdfDPI,
inPaperWidth,
inPaperLength,
arrPdfPages);
int nPages = (int) arrPdfPages.size();
// 用于创建多页TIF的类CMultiTifs 。实现这个类很简单,你只要了解如何使用
// CxImage和CxImageTIF类就行。比如下面的代码创建了一个3页的TIF文件
// multipage.tif:
// CxImage *pimage[3];
// pimage[0]=&image1;
// pimage[1]=&image2;
// pimage[2]=&image3;
// FILE* hFile;
// hFile = fopen("multipage.tif","w+b");
// CxImageTIF multiimage;
// multiimage.Encode(hFile,pimage,3);
// fclose(hFile);
// CMultiTifs类要自己实现
CMultiTifs mtifs(bstrMultiTifs, nPages);
// 打印每一页
for (int iPage=0; iPage<nPages; iPage++)
{
// 当前页结构
PRINT_PDFPAGE pdfPage = arrPdfPages[iPage];
// CImage是ATL71提供的C++类。我觉得这个类的唯一好处就是提供了GetDC()
// 这个东西。这个类存在BUG,这也是我不直接使用它的DC而是使用MemDC的原
// 因。我不会自己创建符合我需要的DC,只好请CImage代劳拉。
CImage imgDC; // 仅仅通过imgDC取得位图DC
BOOL bRes = imgDC.Create(16, 16, 32); // 创建一个小的位图
ATLASSERT(bRes);
// 注意:如果直接使用imgDC的DC,当更换打印DPI时,可能发现只打印一小部
// 分。我觉得它的DC,多次生成有问题。所以使用了HBITMAP转了一下。
HDC hdcImg = imgDC.GetDC(); // 取得位图DC
HDC hMemDC = ::CreateCompatibleDC(hdcImg);
HBITMAP hbmp = ::CreateCompatibleBitmap(hdcImg,
pdfPage.tifPage.cx,
pdfPage.tifPage.cy);
ATLASSERT(hbmp);
// 保存DC...
int nSavedDC = ::SaveDC(hMemDC);
::SelectObject(hMemDC, hbmp);
//
// 填充绘制结构
//
DRAW_INFO di;
di.hDC = hMemDC;
di.crBkgnd = crBkgnd;
// 生成坐标变换类
di.vwp.Init(pdfPage.rcPageView, pdfPage.rcPageData);
//
// 绘制页面
//
DrawMap(&di);
// 恢复DC
::RestoreDC(hMemDC, nSavedDC);
DeleteDC(hMemDC);
imgDC.ReleaseDC();
imgDC.Destroy();
// 使用CxImage重新打开文件
CxImage* pximg = new CxImage();
ATLASSERT(pximg);
bool brc = pximg->CreateFromHBITMAP((HBITMAP) hbmp);
DeleteObject(hbmp); // 使用完毕,删除之
ATLASSERT(brc);
// 添加图片
mtifs.AddPage(pximg);
}
// 创建多页tif
BOOL bRes = mtifs.Encode();
ATLASSERT(bRes);
// 转换TIF到PDF
// 下面的转换使用了开源的libtiff库v3.8.2。
// 下载自:http://www.remotesensing.org/libtiff/
// 你需要包装成自己的方法:tiff2pdf
tiff2pdf(bstrMultiTifs, bstrPdfFileName, pdfDPI, inPaperWidth, inPaperLength);
}
三、结束语
好拉,终于说的差不多了。一个小问题,知道如何计算页面图象的尺寸么?假设打印分辨率是300dpi,而纸张是8.27 x 11.69 inch(A4)大小。则页面图象就是:
宽:8.27*300 = 2481 象素
高:11.69*300 = 3507 象素
这样,你的纸张就是2481 x 3507 大小。你在这张纸上的所有度量都是参照这个大小来进行的。最后,你在把这个页面图象转换成PDF的时候,一定不要忘记告诉它你要的DPI(这里是300)啊!
既然有了PDF,就可以使用Adobe Reader来使用它拉。用Adobe Reader预览你的地图,难道不比自己写一个来的划算么?
我花了很长时间在编辑我的文章,最后显示出来还是不整齐.我没办法拉!而且无法加入图象!遗憾!!
相关文章推荐
- Webgis 打印实现技术细节
- Shadow Volume( 阴影体)渲染技术的实现细节及感受(一)之 阴影体生成
- 怎样自己构建一个小型的Zoomeye----从技术细节探讨到实现
- 如何自己构建一个小型的Zoomeye----从技术细节探讨到实现
- Web系统页面打印技术实现与分析
- 基于百度API的开源自动翻译.srt文件软件的实现的几个技术细节总结附源代码
- 用java语言实现万年历(输入年月,打印当前日历,主要使用Java Swing技术)
- 用java语言实现万年历(输入年月,打印当前日历,主要使用Java Swing技术)
- 基于SVG技术实现WebGIS的基本功能
- ASP.NET中应用XML技术实现Web报表打印
- Kafka 实现的几个技术细节讨论
- WebGIS与SNS技术结合实现 WebGIS应用的新形态
- 执法文书打印的实现(二):基于freemaker技术生成可打印的word文档
- ASP.NET中应用XML技术实现Web报表打印
- 如何构建一个小型的Zoomeye—-从技术细节探讨到实现
- iOS前端与后台交互技术实现及技术细节
- Web系统页面打印技术实现与分析
- 实现二级联动菜单的技术细节
- webgis实现技术分析
- 条码生成与打印的技术实现