ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenLayers导出功能:地图截图+GeoJSON导出完整方案,从Canvas合并到PDF生成

OpenLayers导出功能:地图截图+GeoJSON导出完整方案,从Canvas合并到PDF生成 产品要求用户可以导出当前地图视图为图片。听起来简单但OpenLayers的地图是由多个Canvas图层叠加的直接截图只能拿到最顶层。我花了两天研究Canvas合并、跨域处理、PDF生成这些问题最终实现了一个完整的导出方案。一、地图截图Canvas合并导出1.1 核心原理OpenLayers的地图由多个Canvas图层叠加导出时需要等待所有图层渲染完成创建一个新的Canvas将所有图层的Canvas依次绘制到新Canvas上导出为图片1.2 完整实现constexportMapImage(){map.value.once(rendercomplete,(){constmapCanvasdocument.createElement(canvas)constsizemap.value.getSize()mapCanvas.widthsize[0]mapCanvas.heightsize[1]constmapContextmapCanvas.getContext(2d)// 合并所有图层的CanvasArray.prototype.forEach.call(map.value.getViewport().querySelectorAll(.ol-layer canvas, canvas.ol-layer),(canvas){if(canvas.width0){// 处理透明度constopacitycanvas.parentNode.style.opacity||canvas.style.opacity mapContext.globalAlphaopacity?1:Number(opacity)// 处理CSS变换高分屏适配consttransformcanvas.style.transformconstmatrixtransform.match(/^matrix\(([^\(]*)\)$/)if(matrix){constvaluesmatrix[1].split(,).map(Number)CanvasRenderingContext2D.prototype.setTransform.apply(mapContext,values)}mapContext.drawImage(canvas,0,0)}})mapContext.globalAlpha1// 触发下载constlinkdocument.createElement(a)link.downloadmap-${Date.now()}.pnglink.hrefmapCanvas.toDataURL(image/png)link.click()})// 触发渲染map.value.renderSync()}1.3 关键API说明API用途map.once(rendercomplete, fn)等待渲染完成map.renderSync()触发同步渲染map.getSize()获取地图尺寸canvas.toDataURL(image/png)Canvas转图片二、导出为图片文件2.1 PNG格式默认constlinkdocument.createElement(a)link.downloadmap.pnglink.hrefmapCanvas.toDataURL(image/png)link.click()2.2 JPEG格式文件更小// quality: 0-1越小文件越小质量越低link.hrefmapCanvas.toDataURL(image/jpeg,0.8)link.downloadmap.jpg2.3 高清导出2倍分辨率constexportHiRes(){map.value.once(rendercomplete,(){constsizemap.value.getSize()constscale2// 2倍分辨率constmapCanvasdocument.createElement(canvas)mapCanvas.widthsize[0]*scale mapCanvas.heightsize[1]*scaleconstmapContextmapCanvas.getContext(2d)// 缩放Canvas上下文mapContext.scale(scale,scale)// ... 合并图层代码同上constlinkdocument.createElement(a)link.downloadmap-hires.pnglink.hrefmapCanvas.toDataURL(image/png)link.click()})map.value.renderSync()}三、GeoJSON数据导出3.1 导出绘制的数据import{GeoJSON}fromol/formatconstexportGeoJSON(){constformatnewGeoJSON()constfeaturesdrawSource.getFeatures()constgeojsonformat.writeFeaturesObject(features,{dataProjection:EPSG:3857,featureProjection:EPSG:3857})constblobnewBlob([JSON.stringify(geojson,null,2)],{type:application/json})constlinkdocument.createElement(a)link.downloaddrawings-${Date.now()}.geojsonlink.hrefURL.createObjectURL(blob)link.click()URL.revokeObjectURL(link.href)// 释放内存}3.2 Circle转PolygonGeoJSON不支持Circle导出前需转换constcircleToPolygon(circle,numPoints32){constcentercircle.getCenter()constradiuscircle.getRadius()constcoords[]for(leti0;inumPoints;i){constangle(i/numPoints)*2*Math.PIcoords.push([center[0]radius*Math.cos(angle),center[1]radius*Math.sin(angle)])}coords.push(coords[0].slice())returnnewPolygon([coords])}3.3 复制到剪贴板constcopyToClipboardasync(){constgeojsonformat.writeFeaturesObject(features,{...})try{awaitnavigator.clipboard.writeText(JSON.stringify(geojson,null,2))alert(已复制到剪贴板)}catch(err){alert(复制失败)}}四、导出PDF使用jsPDF4.1 安装依赖npminstalljspdf4.2 实现import{jsPDF}fromjspdfconstexportPDF(){map.value.once(rendercomplete,(){constmapCanvasdocument.createElement(canvas)constsizemap.value.getSize()mapCanvas.widthsize[0]mapCanvas.heightsize[1]constmapContextmapCanvas.getContext(2d)// 合并图层同上Array.prototype.forEach.call(map.value.getViewport().querySelectorAll(.ol-layer canvas, canvas.ol-layer),(canvas){if(canvas.width0){mapContext.globalAlphacanvas.parentNode.style.opacity||1mapContext.drawImage(canvas,0,0)}})// 创建PDFconstimgDatamapCanvas.toDataURL(image/png)constpdfnewjsPDF({orientation:size[0]size[1]?landscape:portrait,unit:px,format:[size[0],size[1]]})pdf.addImage(imgData,PNG,0,0,size[0],size[1])pdf.save(map-${Date.now()}.pdf)})map.value.renderSync()}五、导出带标注的图片5.1 问题导出的图片没有UI控件按钮、图例等。5.2 解决方案将UI元素也绘制到Canvas上constexportWithUI(){map.value.once(rendercomplete,(){constmapCanvasdocument.createElement(canvas)constsizemap.value.getSize()mapCanvas.widthsize[0]mapCanvas.heightsize[1]constctxmapCanvas.getContext(2d)// 1. 绘制地图图层Array.prototype.forEach.call(map.value.getViewport().querySelectorAll(.ol-layer canvas, canvas.ol-layer),(canvas){if(canvas.width0){ctx.globalAlphacanvas.parentNode.style.opacity||1ctx.drawImage(canvas,0,0)}})// 2. 绘制图例ctx.globalAlpha1ctx.fillStylergba(255, 255, 255, 0.9)ctx.fillRect(size[0]-120,size[1]-180,110,170)ctx.fillStyle#333ctx.fontbold 14px sans-serifctx.fillText(图例,size[0]-100,size[1]-155)// ... 绘制图例内容// 3. 绘制标题ctx.fillStylergba(0, 0, 0, 0.7)ctx.fillRect(10,10,200,40)ctx.fillStyle#fffctx.fontbold 16px sans-serifctx.fillText(地图标题,20,38)// 导出constlinkdocument.createElement(a)link.downloadmap-with-ui.pnglink.hrefmapCanvas.toDataURL(image/png)link.click()})map.value.renderSync()}六、踩坑记录坑1导出图片空白现象导出的图片是空白的。原因没有等渲染完成就导出或者没有调用renderSync()。解决map.value.once(rendercomplete,(){...})map.value.renderSync()// 必须调用坑2导出图片有跨域问题现象瓦片图层在导出图片中不显示。原因瓦片URL没有设置crossOrigin。解决newTileLayer({source:newXYZ({url:...,crossOrigin:anonymous// 必须设置})})坑3高分屏导出模糊现象Retina屏幕上导出的图片模糊。原因Canvas尺寸是逻辑像素不是物理像素。解决导出时乘以devicePixelRatioconstdprwindow.devicePixelRatio||1mapCanvas.widthsize[0]*dpr mapCanvas.heightsize[1]*dpr mapContext.scale(dpr,dpr)七、总结功能核心API关键点地图截图rendercomplete Canvas合并必须等渲染完成PNG导出canvas.toDataURL(image/png)最常用JPEG导出canvas.toDataURL(image/jpeg, quality)文件更小高清导出devicePixelRatio缩放Retina屏幕GeoJSON导出GeoJSON.writeFeaturesObject()Circle先转PolygonPDF导出jsPDF Canvas转图片需要额外依赖更新日期2026年9月调试版本OpenLayers 10.9.0
返回列表