Webgis 打印实现技术细节

Webgis 打印实现技术细节
——兼论如何实现打印预览
褫其华衮,示人本相系列之三
cheungmine
在我的上一篇文章《 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)
COLORREFcrBkgnd // 背景色
)
{
// 临时多页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 类要自己实现
CMultiTifsmtifs(bstrMultiTifs, nPages);
// 打印每一页
for (int iPage=0; iPage<nPages; iPage++)
{
// 当前页结构
PRINT_PDFPAGE pdfPage = arrPdfPages[iPage];
// CImage 是ATL71提供的C++类。我觉得这个类的唯一好处就是提供了GetDC()
// 这个东西。这个类存在BUG,这也是我不直接使用它的DC而是使用MemDC的原
// 因。我不会自己创建符合我需要的DC,只好请CImage代劳拉。
CImageimgDC; // 仅仅通过 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);
boolbrc = pximg->CreateFromHBITMAP((HBITMAP) hbmp);
DeleteObject(hbmp); // 使用完毕,删除之
ATLASSERT(brc);
// 添加图片
mtifs.AddPage(pximg);
}
// 创建多页tif
BOOL bRes = mtifs.Encode();
ATLASSERT(bRes);
// 转换TIF到PDF
// 下面的转换使用了开源的libtiff库v3.8.2。
// 你需要包装成自己的方法: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预览你的地图,难道不比自己写一个来的划算么?
我花了很长时间在编辑我的文章,最后显示出来还是不整齐.我没办法拉!而且无法加入图象!遗憾!!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值