OpenLayers 导出地图为PDF

关注我,带你一起学GIS ^

注:当前使用的是 ol [9.2.4] 版本,天地图使用的key请到天地图官网申请,并替换为自己的key

前言

地图导出原理都是获取当前分辨率下的地图尺寸,然后将canvas内容转换为下载地址。将地图导出为图片格式很常见,这次换一种方式,将其导出为PDF格式。

导出地图为图片请参考之前的文章:【OpenLayers地图打印

1. 初始化选择框事件

设置纸张和导出分辨率大小,在layui选择框中监听纸张类型和分辨率大小事件。

// 初始化纸张尺寸和分辨率
let pageFormat = "A4"
let resolution = "72"
// 选择框事件
layui.use(function () {
    var form = layui.form;
    // select 事件
    form.on('select(page-select-filter)'function (data) {
        pageFormat = data.value; // 获得被选中的值
    });

    form.on('select(resolution-select-filter)'function (data) {
        resolution = data.value; // 获得被选中的值
    });
});

2. 初始化纸张类型

从网上找了一下纸张尺寸大小如下图,初始化A0-A5类型纸张。

// 纸张尺寸
const pageType = {
    A0: [1189, 841],
    A1: [841, 594],
    A2: [594, 420],
    A3: [420, 297],
    A4: [297, 210],
    A5: [210, 148],
}

3. 创建导出函数

在导出地图的时候需要将纸张实际尺寸换算为屏幕尺寸,通过将纸张大小乘以输出分辨率然后除以25.4对其进行转换。1inch = 0.0254m = 2.54cm = 25.4mm = (x)dpi = (x)pixel``x为输出分辨率,单位为像素(pixel),即文中72、150、300。从而得到每毫米像素个数为 (x)dpi / 25.4,所以打印尺寸转换为屏幕像素为(pageSize[0] * resolution) / 25.4。即:

// 将打印尺寸转换为屏幕像素
const width = Math.round((pageSize[0] * resolution) / 25.4)
const height = Math.round((pageSize[1] * resolution) / 25.4)

在导出地图前,设置地图视图为打印尺寸大小。地图分辨率为每像素代表多少的实际距离,这就需要用到地图比例尺,即图上距离比上实地距离。在不同的比例尺下,地图分辨率也会相应变化。打印尺寸地图分辨率= 地图分辨率 / scaling。

const size = map.getSize()
const viewResolution = map.getView().getResolution()
{
    // 设置地图为打印尺寸
    const printSize = [width, height]
    map.setSize(printSize)
    const scaling = Math.min(width / size[0], height / size[1])
    // 设置当前比例尺下的地图分辨率
    map.getView().setResolution(viewResolution / scaling)
}

监听地图渲染事件【rendercomplete】,once方法只执行一次。重新创建一个画布元素来保存导出的地图,之后使用jspdf库方法将地图导出为PDF文件。在成功导出地图后,恢复地图视图。

// 监听地图渲染事件(只监听一次)
map.once("rendercomplete"function () {
    const mapCanvas = document.createElement("canvas")
    mapCanvas.width = width
    mapCanvas.height = height
    const mapContext = mapCanvas.getContext("2d")
    Array.prototype.forEach.call(document.querySelectorAll('.ol-layer canvas'), function (canvas) {
        if (canvas.width > 0) {
            const opacity = canvas.parentNode.style.opacity
            mapContext.globalAlpha = opacity === "" ? 1 : Number(opacity)

            const transform = canvas.style.transform
            const matrix = transform
                .match(/^matrix(([^(]*))$/)[1]
                .split(",")
                .map(Number)

            CanvasRenderingContext2D.prototype.setTransform.apply(mapContext, matrix)
            mapContext.drawImage(canvas, 0, 0)
        }
    })
    mapContext.globalAlpha = 1
    mapContext.setTransform(1, 0, 0, 1, 0, 0)
    // console.log("页面pageFormat:", pageFormat);
    const pdf = new jspdf.jsPDF('landscape', undefined, pageFormat)
    pdf.addImage(
        mapCanvas.toDataURL("image/jpeg"),
        "JPEG",
        0,
        0,
        pageSize[0],
        pageSize[1]
    )
    pdf.save("map.pdf")
    // 恢复原地图大小
    map.setSize(size)
    map.getView().setResolution(viewResolution)
})

4. 完整代码

其中libs文件夹下的包需要更换为自己下载的本地包或者引用在线资源。

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>OpenLayers 导出地图为PDF</title>
    <meta charset="utf-8" />

    <link rel="stylesheet" href="../../libs/css/ol9.2.4.css">
    <link rel="stylesheet" href="../../libs/layui/css/layui.css">

    <script src="../../js/config.js"></script>
    <script src="../../libs/js/ol9.2.4.js"></script>
    <script src="../../libs/layui/layui.js"></script>
    <!-- 引入jspdf -->
    <script src="../../libs/js/jspdf-2.5.1.umd.min.js"></script>
    <style>
        * {
            padding: 0;
            margin: 0;
            font-size: 14px;
            font-family: '微软雅黑';
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        #map {
            position: absolute;
            top: 50px;
            bottom: 0;
            width: 100%;
        }

        #top-content {
            position: absolute;
            z-index: 99;
            width: 100%;
            height: 50px;
            line-height: 50px;
            background: linear-gradient(135deg, #ff00cc, #ffcc00, #00ffcc, #ff0066);
            color: #fff;
            text-align: center;
            font-size: 32px;
        }

        #top-content span {
            font-size: 32px;
        }

        .export-container {
            position: absolute;
            top: 10%;
            left: 20px;
            width: 20%;
            background: #fff;
            border-radius: 2.5px;
            border: 1px solid #ddd;
            /* overflow-y: auto; */
            max-height: 80%;
        }

        .image-style-content {
            margin: 5px 25px;
        }

        .text-title {
            padding: 10px;
            margin-bottom: 15px;
            background: #16baaa;
            color: #fff;
        }

        .image-title {
            padding: 10px;
            margin-bottom: 15px;
            background: #16baaa;
            color: #fff;
        }

        .layer-prop-item {
            display: flex;
            justify-content: space-between;
            flex-direction: row;
            margin: 10px 0;
        }

        .layer-slider-item {
            width: 45%;
            margin-top: 16px;
        }

        .layui-form-label {
            /* padding: 8px 15px; */
            width: 50%;
            height: 38px;
            line-height: 20px;
            box-sizing: border-box;
            border-width: 1px;
            border-style: solid;
            border-radius: 2px 0 0 2px;
            text-align: center;
            background-color: #fafafa;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
            border-color: #eee;
            font-weight: 400;
        }

        .layui-btn-primary {
            width: 50%;
        }

        .layui-btn-export {
            margin: 10px 0;
            width: 100%;
        }
    </style>
</head>

<body>
    <div id="top-content">
        <span>OpenLayers 导出地图为PDF</span>
    </div>
    <div id="map"></div>
    <div class="export-container">
        <h3 for="" class="image-title">地图导出</h3>
        <div class="image-style-content layui-form layui-row layui-col-space16">
            <div class='layer-prop-item '>
                <label class="layui-form-label">纸张类型:</label>
                <div class="layui-col-md6">
                    <select lay-filter="page-select-filter">
                        <option value="A0" selected>A0</option>
                        <option value="A1">A1</option>
                        <option value="A2">A2</option>
                        <option value="A3">A3</option>
                        <option value="A4">A4</option>
                        <option value="A5">A5</option>
                    </select>
                </div>
            </div>
            <div class='layer-prop-item'>
                <label class="layui-form-label">分辨率(dpi):</label>
                <div class="layui-col-md6">
                    <select lay-filter="resolution-select-filter">
                        <option value="72" selected>72</option>
                        <option value="150">150</option>
                        <option value="300">300</option>
                    </select>
                </div>
            </div>
            <button type="button" class="layui-btn layui-btn-export">导出PDF</button>
        </div>
    </div>
</body>

</html>

<script>
    //地图投影坐标系
    const projection = ol.proj.get('EPSG:3857');
    //==============================================================================//
    //============================天地图服务参数简单介绍==============================//
    //================================vec:矢量图层==================================//
    //================================img:影像图层==================================//
    //================================cva:注记图层==================================//
    //======================其中:_c表示经纬度投影,_w表示球面墨卡托投影================//
    //==============================================================================//
    const TDTImgLayer = new ol.layer.Tile({
        title: "天地图影像图层",
        source: new ol.source.XYZ({
            url: "http://t0.tianditu.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=" + TDTTOKEN,
            attibutions: "天地图影像描述",
            crossOrigin: "anoymous",
            wrapX: false
        })
    })
    const TDTImgCvaLayer = new ol.layer.Tile({
        title: "天地图影像注记图层",
        source: new ol.source.XYZ({
            url: "http://t0.tianditu.com/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=" + TDTTOKEN,
            attibutions: "天地图注记描述",
            crossOrigin: "anoymous",
            wrapX: false
        })
    })

    const map = new ol.Map({
        target: "map",
        loadTilesWhileInteracting: true,
        view: new ol.View({
            center: [102.845864, 25.421639],
            zoom: 8,
            worldsWrap: false,
            minZoom: 1,
            maxZoom: 20,
            projection: 'EPSG:4326',
        }),
        layers: [TDTImgLayer],
        // 地图默认控件
        controls: ol.control.defaults.defaults({
            zoom: false,
            attribution: false,
            rotate: false
        })
    })
    {
        // 地图交互控件
        const select = new ol.interaction.Select({
            style: new ol.style.Style({
                fill: new ol.style.Fill({
                    color: "#ffcc009e"
                }),
                stroke: new ol.style.Stroke({
                    color: "#00ffcc",
                    width: 2.5
                })
            })
        })
        const translate = new ol.interaction.Translate({
            features: select.getFeatures()
        })
        // 添加控件
        map.addInteraction(select)
        map.addInteraction(translate)
    }

    {
        // 文字样式
        const labelStyle = new ol.style.Style({
            text: new ol.style.Text({
                font: '12px Calibri,sans-serif',
                overflow: true,
                // 填充色
                fill: new ol.style.Fill({
                    color: "#FAFAD2"
                }),
                // 描边色
                stroke: new ol.style.Stroke({
                    color: "#2F4F4F",
                    width: 3,
                }),
            })
        })
        // 行政区样式
        const regionStyle = new ol.style.Style({
            fill: new ol.style.Fill({
                // color: 'rgba(255, 255, 255, 0.6)',
                color: [230, 230, 250, 0.25],
            }),
            stroke: new ol.style.Stroke({
                color: "#00FFFF",
                width: 1.25,
            }),
        })
        // 添加图层
        const style = [labelStyle, regionStyle]
        const JSON_URL = "https://geo.datav.aliyun.com/areas_v3/bound/530000_full.json"

        fetch(JSON_URL)
            .then(response => response.json())
            .then(result => {
                console.log(result)
                const layer = new ol.layer.Vector({
                    source: new ol.source.Vector({
                        // url: JSON_URL,
                        // format: new ol.format.GeoJSON(),
                        features: (new ol.format.GeoJSON()).readFeatures(result)
                    }),
                    style: function (feature) {
                        const label = feature.get("name").split(" ").join("\n")
                        labelStyle.getText().setText(label)
                        return style
                    },
                    declutter: true
                })
                map.addLayer(layer)
                map.getView().setCenter([101.485106, 25.008643])
                map.getView().setZoom(6.5)
            })
    }

    // 初始化纸张尺寸和分辨率
    let pageFormat = "A4"
    let resolution = "72"
    // 选择框事件
    layui.use(function () {
        var form = layui.form;
        // select 事件
        form.on('select(page-select-filter)'function (data) {
            pageFormat = data.value; // 获得被选中的值
        });

        form.on('select(resolution-select-filter)'function (data) {
            resolution = data.value; // 获得被选中的值
        });
    });

    // 纸张尺寸
    const pageType = {
        A0: [1189, 841],
        A1: [841, 594],
        A2: [594, 420],
        A3: [420, 297],
        A4: [297, 210],
        A5: [210, 148],
    }

    // 导出地图为PDF
    function exportMap2PDF() {
        const pageSize = pageType[pageFormat]
        // console.log("页面尺寸:", pageSize);

        const width = Math.round((pageSize[0] * resolution) / 25.4)
        const height = Math.round((pageSize[1] * resolution) / 25.4)

        const size = map.getSize()
        const viewResolution = map.getView().getResolution()
        {
            // 设置地图为打印尺寸
            const printSize = [width, height]
            map.setSize(printSize)
            const scaling = Math.min(width / size[0], height / size[1])
            map.getView().setResolution(viewResolution / scaling)
        }
        // 监听地图渲染事件(只监听一次)
        map.once("rendercomplete"function () {
            const mapCanvas = document.createElement("canvas")
            mapCanvas.width = width
            mapCanvas.height = height
            const mapContext = mapCanvas.getContext("2d")
            Array.prototype.forEach.call(document.querySelectorAll('.ol-layer canvas'), function (canvas) {
                if (canvas.width > 0) {
                    const opacity = canvas.parentNode.style.opacity
                    mapContext.globalAlpha = opacity === "" ? 1 : Number(opacity)

                    const transform = canvas.style.transform
                    const matrix = transform
                        .match(/^matrix\(([^\(]*)\)$/)[1]
                        .split(",")
                        .map(Number)

                    CanvasRenderingContext2D.prototype.setTransform.apply(mapContext, matrix)
                    mapContext.drawImage(canvas, 0, 0)
                }
            })
            mapContext.globalAlpha = 1
            mapContext.setTransform(1, 0, 0, 1, 0, 0)
            // console.log("页面pageFormat:", pageFormat);
            const pdf = new jspdf.jsPDF('landscape', undefined, pageFormat)
            pdf.addImage(
                mapCanvas.toDataURL("image/jpeg"),
                "JPEG",
                0,
                0,
                pageSize[0],
                pageSize[1]
            )
            pdf.save("map.pdf")
            // 恢复原地图大小
            map.setSize(size)
            map.getView().setResolution(viewResolution)
        })
    }

    // 地图导出
    const exportPDFBtn = document.querySelector(".layui-btn-export")
    exportPDFBtn.addEventListener('click', exportMap2PDF)
</script>

OpenLayers示例数据下载,请在公众号后台回复:ol数据

全国信息化工程师-GIS 应用水平考试资料,请在公众号后台回复:GIS考试

GIS之路公众号已经接入了智能助手,欢迎大家前来提问。

欢迎访问我的博客网站-长谈GIShttp://shanhaitalk.com

都看到这了,不要忘记点赞、收藏+关注 

本号不定时更新有关 GIS开发  相关内容,欢迎关注 

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部