基于leaflet完成框选功能(不随地图缩放)并截图打印

给定一个矩形框用于规定地图打印范围,并截图打印该范围,用户可以在此范围内进行标绘,需要满足以下要求:

1)初始状态下,矩形框不随着地图的放大、缩小、移动而变化位置;(解锁状态)

2)点击锁定按钮后,矩形框要随着地图的放大、缩小、移动而变化位置;

3)锁定与解锁可以切换;

4)添加经纬网格,并标注该线的经纬度;

5)框的宽高比与A3、A4纸相同。

20230324-截图

一、绘制矩形框

1、初始状态——解锁状态——矩形框不随着地图的放大、缩小、移动而变化位置

        思路:地图经纬度坐标与屏幕像素坐标转换。(这里给小白解释一下,屏幕像素坐标指的是在屏幕上像素点的坐标,原点需要自己验证具体位置,我的屏幕坐标原点是左上角,x轴是水平方向,y轴是垂直方向。想了解更多可以问问度娘)

        1、1初始进入地图获取中心点,根据矩形宽高确定左上角、右下角的经纬度,利用leaflet绘制出矩形;

        1、2将该经纬度转成屏幕像素坐标,保存起来;(这样无论怎么操作地图,矩形框在屏幕上的位置是定好的。)

        1、3当我们操作地图[放大、缩小、移动]后,我们就把像素坐标转为经纬度坐标,再利用leaflet绘制出矩形。

//绘制矩形函数,需要时直接调用就行
drawRectangle(bounds) {
    //判断矩形是否存在,存在就移除
    if (this.layergroupPolygon) this.layergroup.removeLayer(this.layergroupPolygon)
    //根据传来的bounds,重新绘制矩形
    this.layergroupPolygon = L.rectangle(bounds, { weight: 2 }).addTo(this.layergroup);
    //矩形框背景颜色透明度为0,也就是没有背景颜色。这一步个人取舍
    this.layergroupPolygon.setStyle({
        opacity: 1,
        fillOpacity: 0,
    })
},

//初始化的准备
getCenter() {
   //是否为锁定状态
   this.isLock = false

   //获取当前页面的地图中心点
   let center = this.bindMap.view.center
   
   //定义距离中心点的上边距、左边距;方便得到矩形各个顶点的经纬度
   //这个距离我是按照A4的比例定的,有需要A3A2的纸张比例的可以自己写一个下拉框,优化即可
   let top = 15
   let left = top * 1.414
   //计算左上角、右下角的经纬度
   let topx = center[0] - left
   let topy = center[1] + top
   let bottomx = center[0] + left
   let bottomy = center[1] - top

   //注意:leaflet的经纬度坐标是纬度在前,经度在后
   this.top_latlng = [topy, topx]
   this.bottom_latlng = [bottomy, bottomx]

   //计算左上角、右下角的像素坐标——经纬度转像素坐标,可查看leaflet官网
   this.screenCoor1 = this.bindMap.latLngToContainerPoint([topx, topy])
   this.screenCoor2 = this.bindMap.latLngToContainerPoint([bottomx, bottomy])

   //图层不存在时再创建
   if (!this.layergroup) {
      this.layergroup = L.layerGroup().addTo(this.bindMap.map);
   }
   
   //调用经纬度坐标,在地图上绘制矩形
   let bounds = [this.top_latlng, this.bottom_latlng]
   this.drawRectangle(bounds)

   //监听地图变化,变化且未锁定时更新矩形
   this.bindMap.map.on('move zoom', () => {
      if (!this.isLock) this.update()
   })
},

// 更新地理区域
update() {
   //更新矩形时,要先利用 保存起来的屏幕坐标 转为 当前地图状态的经纬度 再画图
   this.top_latlng = this.bindMap.map.containerPointToLatLng(this.screenCoor1)
   this.bottom_latlng = this.bindMap.map.containerPointToLatLng(this.screenCoor2)
   //修改矩形信息
   this.layergroupPolygon.setBounds([this.top_latlng, this.bottom_latlng])
},

2、点击锁定按钮后,矩形框要随着地图的放大、缩小、移动而变化位置;

        思路:点击锁定,保存当前经纬度不再变化,也就是不让像素坐标转为经纬度坐标。地图监听事件不再更新画矩形。

// 锁定框选
clockReact() {
   //点击锁定,再次点击解锁
   this.isLock = !this.isLock
   this.update()
},

二、添加经纬网

经纬网代码可以根据自己需要修改间距、颜色等样式等。

// 是否添加经纬线
isAddLatlng() {
   //根据isAdd 判断是否添加经纬线
   this.isAdd = !this.isAdd

   // 创建经纬线图层
   if (!this.lonLatGridLineLayer)
      this.lonLatGridLineLayer = L.featureGroup().addTo(this.bindMap.map);
   if (this.isAdd) {
      this.addLatlng()
   } else {
      this.lonLatGridLineLayer.clearLayers();
   }
}, 
// 添加经纬线
addLatlng() {
   if (!this.isShowReact) return this.$message.warning('请先选取范围')
      // 添加经纬线
      let addLonLatLine = () => {
         let zoom = this.bindMap.map.getZoom();
         let bounds = this.layergroupPolygon.getBounds();
         let north = bounds.getNorth();
         let east = bounds.getEast();
         // 经纬度间隔
         let d = 90 / Math.pow(2, zoom - 1);
         // 经线网格
         for (let index = -180; index <= 360; index += d) {
             // 判断当前视野内
             if (bounds.contains([north, index])) {
                // 绘制经线
                let lonLine = L.polyline(
                    [
                        [-90, index],
                        [90, index],
                    ],
                { weight: 1, color: "blue" }
                );
            this.lonLatGridLineLayer.addLayer(lonLine);
            // 标注
            let text = index.toFixed(1) + "°";
            // 动态计算小数位数
            if (zoom > 10) {
                text = index.toFixed((zoom - 8) / 2) + "°";
            }
            let divIcon = L.divIcon({
                html: `<div style="white-space: nowrap;color:red;">${text}</div>`,
                            iconAnchor: [0, -5],
                        });
           let textMarker = L.marker([north, index], { icon: divIcon });
           this.lonLatGridLineLayer.addLayer(textMarker);
         }
       }
       if (d > 90) d = 90;
       // 纬线网格
       for (let index = -90; index <= 90; index += d) {
          if (bounds.contains([index, east])) {
             let lonLine = L.polyline(
                 [
                    [index, -180],
                    [index, 360],
                 ],
             { weight: 1, color: "blue" }
             );
          this.lonLatGridLineLayer.addLayer(lonLine);
          // 标注
          let text = index.toFixed(1) + "°";
          if (zoom > 10) {
             text = index.toFixed((zoom - 8) / 2) + "°";
          }
          let divIcon = L.divIcon({
              html: `<div style="white-space: nowrap;color:red;">${text}</div>`,
                            iconAnchor: [(text.length + 1) * 6, 0],
                        });
          let textMarker = L.marker([index, east], { icon: divIcon });
              this.lonLatGridLineLayer.addLayer(textMarker);
          }
       }
     }
     addLonLatLine();

     this.bindMap.map.on("zoomend move", () => {
         this.lonLatGridLineLayer.clearLayers();
         addLonLatLine();
     });
  },

三、截图打印

        思路:把矩形框里面的东西截取下来

        但是查遍插件都需要获取DOM元素,再进行截图。基于这个情况,我们可以使用插件获取地图的截图,再利用画布的裁剪功能裁出来我们想要的范围。

        这里需要验证你的像素坐标位置是否正确。

        下载插件:npm install dom-to-image

        引用插件:import domtoimage from "dom-to-image"; 哪个页面使用放到哪个页面就可以

        // 获取截图
        getCutPic() {
            //使用插件domtoimage
            domtoimage
                .toPng(this.bindMap.$el)
                .then((dataUrl) => {
                    console.log(dataUrl);
                    this.imgUrl = dataUrl;
                    this.secondCut()
                })
                .catch(function (error) {
                    console.error("oops, something went wrong!", error);
                });
        },
        //二次截图
        async secondCut() {
            //this.handleSrc是存放最终截图效果的位置
            this.handleSrc = await this.handleImg({
                src: this.imgUrl,
                rect: {
                    x: this.screenCoor1.x, // 截取方格左上角横坐标
                    y: this.screenCoor1.y, // 截取方格左上角纵坐标
                    width: this.screenCoor2.x - this.screenCoor1.x, // 截取方格宽度
                    height: this.screenCoor2.y - this.screenCoor1.y // 截取方格高度
                }
            })
        },
        handleImg(opts) {
            return new Promise((resolve, reject) => {
                console.log(opts);
                const { src, rect } = opts
                if (!src || !rect) {
                    reject(new Error('opts params Error!'))
                }
                const img = new Image()
                img.src = src
                img.onload = function () {
                    const canvas = document.createElement('canvas')
                    const ctx = canvas.getContext('2d')
                    const { x, y, width, height } = rect
                    canvas.width = width
                    canvas.height = height
                    ctx.drawImage(this, x, y, width, height, 0, 0, width, height)
                    const url = canvas.toDataURL('image/png')
                    resolve(url)
                }
                img.onerror = function (err) {
                    reject(err)
                }
            })
        },

至此,代码已经书写完毕,本人也是小白,有错误或者需要优化的可以随时联系我,希望我的文章对大家有帮助,谢谢!

猜你喜欢

转载自blog.csdn.net/weixin_47192981/article/details/129749383