您的位置:首页 > 其它

Webgis 打印实现技术细节

2006-12-22 15:15 190 查看
[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预览你的地图,难道不比自己写一个来的划算么?

我花了很长时间在编辑我的文章,最后显示出来还是不整齐.我没办法拉!而且无法加入图象!遗憾!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: