You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenLayers如何打印指定区域为PDF并实现多页打印?

OpenLayers 指定区域多页PDF打印问题

在OpenLayers中,需要实现指定区域(非整个Canvas)的多页/多区域PDF打印,但目前遇到两个问题:

  1. 使用map.once("rendercomplete")方法渲染耗时较长;
  2. 循环调用该方法实现多页打印时,仅能输出一页。

以下是当前实现的打印方法:

async generatePDF() {
 try {
 let items = this.workspaceFacade.source.getFeatures();
 let finalOutput = [];
 let allExtent = [];
 let printArea = [];
  if (items.length > 0) {
    items.forEach((item) => {
      if (item.getGeometry().getType() == "Polygon") {
        item.setStyle(frameStylePrint());
        let extent = item.getGeometry().getExtent();
        allExtent.push(extent);
      }});
    }
    allExtent.map(async (item, index) => {
      this.workspaceFacade.map
        .getView()
        .fit(item, [1000, 900], { padding: [-10, 10, 0, -10] });
      let legendurl = this.LEGEND_URL;
      let htmlcont = this.htmlContent;
      let svgString, htmlButton, htmlLegend, htmlString;
      this.workspaceFacade.map.once("rendercomplete", function () {
        const mapCanvas = document.createElement("canvas");
        mapCanvas.width = 1000;
        mapCanvas.height = 900;
        const mapContext = mapCanvas.getContext("2d");
        Array.prototype.forEach.call(
          document.querySelectorAll(".ol-layer canvas, canvas.ol-layer"),

          function (canvas) {
            if (canvas.width > 0) {
              const opacity =
                canvas.parentNode.style.opacity || canvas.style.opacity;
              mapContext.globalAlpha = opacity === "" ? 1 : Number(opacity);
              let matrix;
              const transform = canvas.style.transform;
              if (transform) {
                // Get the transform parameters from the style's transform matrix
                matrix = transform
                  .match(/^matrix\(([^\(]*)\)$/)[1]
                  .split(",")
                  .map(Number);
              } else {
                matrix = [
                  parseFloat(canvas.style.width) / canvas.width,
                  0,
                  0,
                  parseFloat(canvas.style.height) / canvas.height,
                  0,
                  0,
                ];
              }
              // Apply the transform to the export map context
              CanvasRenderingContext2D.prototype.setTransform.apply(
                mapContext,
                matrix
);
const backgroundColor =
canvas.parentNode.style.backgroundColor;
if (backgroundColor) {
mapContext.fillRect(0, 0, canvas.width, canvas.height);
}
mapContext.drawImage(canvas, 0, 0);
}
}
);
mapContext.globalAlpha = 1;
mapContext.setTransform(1, 0, 0, 1, 0, 0);
mapCanvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
console.log("fourth", url, item);
htmlcont = `<div><img width="500" height="700" src="${url}" style="float:left; border: 1px solid black"></div>`;
console.log(htmlcont);
svgString = `<div>${htmlcont} </div>`;
htmlButton =
            `<div><input onclick="window.print()" id="button" type="button" value="Generate pdf" style="background-color: #5E7B8D;` +
            `color: #FFFFFF; border-radius: 10px; position: relative;"></div>`; 
htmlLegend = `<div><img width="200" height="700" src="${legendurl}" style="float:left; border: 1px solid black"></div>`;
console.log(svgString);
htmlString = `<div id="planimetric">
${htmlButton}
${svgString}                                 
${htmlLegend}
</div>`;
printArea[index] = htmlString;
const planimetricView = window.open('', '_blank');
planimetricView.document.write(htmlString);
planimetricView.document.title = 'Preliminary Project - Secondary Network Elements';
planimetricView.document.close();
console.log(printArea);
});
});
// return printArea;
}
// )
);
// console.log(printArea1);
console.log(this.LEGEND_URL);
} catch (error) {
throw error;
}
}

问题原因分析

  1. 并发渲染冲突:使用map()循环结合async时,所有视图拟合操作会同时触发,rendercomplete回调会被后续操作覆盖,最终仅执行最后一次渲染。
  2. 多窗口干扰:每次渲染完成后直接打开新窗口,不仅可能被浏览器拦截,也无法将多页内容整合为单个PDF文件。
  3. 异步逻辑未闭环:toBlob是异步操作,但未做等待处理,导致内容收集不完整。

解决方案与优化代码

通过串行处理打印任务、统一收集内容后生成单窗口打印页面,解决多页失效问题,同时优化渲染效率:

async generatePDF() {
  try {
    const items = this.workspaceFacade.source.getFeatures();
    const allExtent = [];
    const printContents = [];
    const legendUrl = this.LEGEND_URL;

    // 收集所有需要打印的多边形范围
    if (items.length > 0) {
      items.forEach(item => {
        if (item.getGeometry().getType() === "Polygon") {
          item.setStyle(frameStylePrint());
          allExtent.push(item.getGeometry().getExtent());
        }
      });
    }

    // 串行处理每个打印区域,避免渲染冲突
    for (const [index, extent] of allExtent.entries()) {
      // 等待视图拟合并渲染完成
      await new Promise(resolve => {
        this.workspaceFacade.map.getView().fit(extent, [1000, 900], { padding: [-10, 10, 0, -10] });
        this.workspaceFacade.map.once("rendercomplete", resolve);
      });

      // 导出当前视图为Canvas图片
      const mapCanvas = document.createElement("canvas");
      mapCanvas.width = 1000;
      mapCanvas.height = 900;
      const mapContext = mapCanvas.getContext("2d");

      Array.prototype.forEach.call(
        document.querySelectorAll(".ol-layer canvas, canvas.ol-layer"),
        canvas => {
          if (canvas.width <= 0) return;
          const opacity = canvas.parentNode.style.opacity || canvas.style.opacity;
          mapContext.globalAlpha = opacity === "" ? 1 : Number(opacity);

          let matrix;
          const transform = canvas.style.transform;
          if (transform) {
            matrix = transform.match(/^matrix\(([^\(]*)\)$/)[1].split(",").map(Number);
          } else {
            matrix = [
              parseFloat(canvas.style.width) / canvas.width,
              0,
              0,
              parseFloat(canvas.style.height) / canvas.height,
              0,
              0,
            ];
          }

          mapContext.setTransform(...matrix);
          const backgroundColor = canvas.parentNode.style.backgroundColor;
          if (backgroundColor) {
            mapContext.fillStyle = backgroundColor;
            mapContext.fillRect(0, 0, canvas.width, canvas.height);
          }
          mapContext.drawImage(canvas, 0, 0);
        }
      );

      mapContext.globalAlpha = 1;
      mapContext.setTransform(1, 0, 0, 1, 0, 0);

      // 等待Blob生成完成
      const blob = await new Promise(resolve => mapCanvas.toBlob(resolve));
      const url = URL.createObjectURL(blob);

      // 组装单页打印内容,添加分页标记
      const pageContent = `
        <div style="page-break-after: always; margin: 20px 0;">
          <img width="500" height="700" src="${url}" style="border: 1px solid black;">
          <img width="200" height="700" src="${legendUrl}" style="border: 1px solid black; margin-left: 20px;">
        </div>
      `;
      printContents.push(pageContent);
    }

    // 生成包含所有页面的打印HTML
    const printHtml = `
      <html>
        <head>
          <title>Preliminary Project - Secondary Network Elements</title>
          <style>
            @media print {
              .print-btn { display: none; }
            }
          </style>
        </head>
        <body>
          <button class="print-btn" onclick="window.print()" style="background-color: #5E7B8D; color: #FFFFFF; border-radius: 10px; padding: 8px 16px;">生成PDF</button>
          ${printContents.join('')}
        </body>
      </html>
    `;

    // 打开统一打印窗口
    const printWindow = window.open('', '_blank');
    printWindow.document.write(printHtml);
    printWindow.document.close();

  } catch (error) {
    console.error('打印PDF失败:', error);
    throw error;
  }
}

优化说明

  • 串行处理:使用for...of循环结合Promise,确保每个区域的渲染、导出完成后再处理下一个,避免回调覆盖。
  • 统一生成PDF:收集所有页面内容后一次性生成HTML,确保所有页面在同一个窗口中,支持浏览器原生的多页PDF打印。
  • 渲染优化:简化Canvas变换逻辑,修复背景色填充的样式缺失问题,减少无效渲染操作。
  • 分页控制:添加page-break-after样式,确保每个打印区域自动分页。

内容的提问来源于stack exchange,提问作者Surender Rengaraj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 17:47:12