Complete the frame selection function based on leaflet (not zooming with the map) and print the screenshot

A rectangular frame is given to specify the map printing range, and a screenshot is taken to print the range. Users can plot within this range, and the following requirements need to be met:

1) In the initial state , the rectangular frame does not change position as the map is zoomed in, zoomed out, or moved; (unlocked state)

2) After clicking the lock button, the rectangular frame will change its position as the map zooms in, zooms out, and moves;

3) Lock and unlock can be switched;

4) Add a latitude and longitude grid, and mark the latitude and longitude of the line;

5) The aspect ratio of the frame is the same as that of A3 and A4 paper.

20230324-Screenshot

1. Draw a rectangular frame

1. Initial state - unlocked state - the rectangular frame does not change position with the zoom in, zoom out, or move of the map

        Idea: Convert the latitude and longitude coordinates of the map to the screen pixel coordinates. (Here, explain to Xiaobai that the screen pixel coordinates refer to the coordinates of the pixels on the screen. The origin needs to be verified by yourself. The origin of my screen coordinates is the upper left corner, the x-axis is the horizontal direction, and the y-axis is the vertical direction. If you want to know more, you can ask Du Niang)

        1.1 Initially enter the map to obtain the center point, determine the latitude and longitude of the upper left corner and lower right corner according to the width and height of the rectangle, and use leaflet to draw the rectangle;

        1, 2 Convert the latitude and longitude into screen pixel coordinates and save them; (No matter how you operate the map, the position of the rectangle on the screen is fixed.)

        1, 3 When we operate the map [zoom in, zoom out, move] , we convert the pixel coordinates into latitude and longitude coordinates, and then use leaflet to draw a rectangle.

//绘制矩形函数,需要时直接调用就行
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. After clicking the lock button, the rectangular frame will change its position as the map is zoomed in, zoomed out, and moved;

       Idea: Click Lock to save the current latitude and longitude without changing, that is, to prevent pixel coordinates from being converted to latitude and longitude coordinates. The map listener event no longer updates the drawing rectangle.

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

2. Add the graticule

The graticule code can modify the spacing, color and other styles according to your needs.

// 是否添加经纬线
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();
     });
  },

3. Screenshot printing

        Idea: intercept the things inside the rectangular box

       But you need to get the DOM element to check all the plug-ins, and then take a screenshot. Based on this situation, we can use the plug-in to obtain a screenshot of the map, and then use the cropping function of the canvas to crop out the range we want.

        Here you need to verify whether your pixel coordinate position is correct.

        Download the plugin: npm install dom-to-image

        Reference plug-in: import domtoimage from "dom-to-image"; which page can be used and placed on which page

        // 获取截图
        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)
                }
            })
        },

So far, the code has been written. I am also a novice. If you have any mistakes or need optimization, you can contact me at any time. I hope my article can be helpful to everyone, thank you!

Guess you like

Origin blog.csdn.net/weixin_47192981/article/details/129749383