Live2D

leaflet测绘功能,实现下载测绘结果图

做一个平面地图的测绘功能,可在测绘图片上设定比例尺、测距,计算面积、规划区域等,并将最终测绘结果下载为png图片(原图)。

项目地址:https://github.com/kikiy7/leaflet-image-draw

 

一、leaflet创建绘图工具

基于:leaflet-image-hotspots   实现热点图像区域编辑功能,能够在图片上绘制、编辑、删除图形区域以及文字说明

API 参考:单张图纸(主要的绘图API)

     map地图API(地图功能)

 

 1.创建地图

map = new L.Map(domId, {
  editable: true,
  crs: L.CRS.Simple, //注:若不了解该属性请勿随意更改
  maxZoom : 6,
  minZoom : -2,
  center : [ $('#'+domId).width() / 2, $('#'+domId).height() / 2 ]
});

2.创建绘图工具

var drawControl = new L.Control.Draw({
  position: 'topright',
  draw: {
    polyline: {
      showLength:true,
      metric:true
    },
    polygon:  {
      allowIntersection: false, // Restricts shapes to simple polygons
      showArea: true,
      drawError: {
        color: '#e1e100', // Color the shape will turn when intersects
        message: '<strong>错误<strong>,你不能这么画!'
      },
      shapeOptions: {
        weight : weight,
      }
    },
    rectangle: {
      shapeOptions: {
      weight : weight
      }
    },
    circle:false, //关闭画圆功能
    marker:false  //关闭标记功能
  },
  edit: {
    featureGroup: drawnItems,
    poly: {
      allowIntersection: false
    },
    remove: true
  }
});
//将绘图工具添加到地图上 if(map.addControl(drawControl)){   callback(); }

 

二、 面积及路径的计算

1.面积计算公式

  一开始是使用leaflet自带的 L.GeometryUtil.geodesicArea() 计算面积方法,和 L.latLng().distanceTo() 计算两点间的距离的方法,发现当在地图模式为 crs: L.CRS.Simple, 也就是本案例中所用的地图模式,其leaflet自带的经纬度面积的算法不适用于该平面测绘,所以另外找了一个计算任意多边形面积的方法

计算长度:

function computeLength(line){
  let dis = 0;
  for (let i = 0; i <line.length - 1;i++){
    let start = line[i];
    let end = line[i+1];
    let dx = start.lng - end.lng;
    let dy = start.lat -end.lat;
    dis += Math.sqrt(dx*dx + dy*dy);
  }
  return dis;
}

计算面积:

function computePolygonArea(lnglats) {
  let length = lnglats.length;
  let s = lnglats[0].lng * (lnglats[length - 1].lat - lnglats[1].lat);
  for (let i =  1;i < length; i++){
    s += lnglats[i].lng * (lnglats[i-1].lat - lnglats[(i+1) % length].lat);
  }
  return Math.abs(s/2);
}

由于本案例是直接采用L.Control.Draw 集成绘图工具控制器,监听绘图创建事件,只需要在绘制某图形结束时获取 layer.getLatLngs() 并传入方法即可。

//监听绘图工具创建图形事件
map.on(L.Draw.Event.CREATED, function (e) {
    var type = e.layerType,
        layer = e.layer;
    //drawnItems.addLayer(layer);
    layer.addTo(drawnItems);

    console.log(scale_length)
    if (scale_length == 0){
        layer.setText("请先设置比例尺");
    }else {
        if (type === 'marker') {
            swal({
                title: '标记',
                input: 'text',
                showCancelButton: true,
                inputValidator: function (value) {
                    return new Promise(function (resolve, reject) {
                        if (value) {
                            resolve();
                        } else {
                            reject('你得填一下你标记了啥!');
                        }
                    })
                }
            }).then(function (result) {
                pointAttr.push({
                    'leaflet_id': layer._leaflet_id,
                    'type': type
                });
                layer.bindLabel(result);
                swal({
                    type: 'success',
                    html: '您标记了:' + result
                });
            }, function (dismiss) {
                drawnItems.removeLayer(layer);
            });
        } else {
            let latlng = layer.getLatLngs();
            if (type === "polyline") {
                let distance = (computeLength(latlng) / scale_length * scale).toFixed(2);
                layer.setText(distance + "m");
            } else {
                let area = (computePolygonArea(latlng) / scale_length / scale_length * scale * scale).toFixed(2);
                layer.setText(area + "m²");
            }
        }
    }
    swal({
        title: '绘制成功',
        showCancelButton: true,
    }).then(function (result) {
        pointAttr.push({
            'leaflet_id': layer._leaflet_id,
            'type': type
        });
    }, function (dismiss) {
        drawnItems.removeLayer(layer);
    });
});

//监听绘图工具编辑图形事件
map.on(L.Draw.Event.EDITED, function (e) {
    var layers = e.layers;
    var countOfEditedLayers = 0;
    layers.eachLayer(function (layer) {
        resetText(layer);
        countOfEditedLayers++;
    });
    console.log("修改了 " + countOfEditedLayers + " 个图层");
});

 

2.设置比例尺

点击比例尺时激活地图点击事件

function createScale(e) {
    e.stopPropagation();
    let sLen = scale_geometry.length;
    if(sLen>0){
        for (let i = 0 ; i<sLen; i++){
            map.removeLayer(scale_geometry[i]);
        }
    }
    scale_line = L.polyline(scaleArr,{color:'#000000'});
    map.on("click",addScale);
}

添加点,存入scale_geometry数组以便在重设比例尺时移除地图上的比例尺图形。

function addScale(e){
    scaleArr.push([e.latlng.lat, e.latlng.lng]);
    scale_line.addLatLng(e.latlng);
    map.addLayer(scale_line);
    const node=new L.circleMarker(e.latlng , { color: '#363333', fillColor: '#363030', fillOpacity: 1 ,radius:5 });
    map.addLayer(node);
    scale_geometry.push(node);
    map.on('mousemove', scale_lineMove);//双击地图
    if (scaleArr.length == 2){
        scale_end();
    }
}

鼠标移动跟随线段

function scale_lineMove(e){
    if (scaleArr.length > 0) {
        let ls = [scaleArr[scaleArr.length - 1], [e.latlng.lat, e.latlng.lng]]
        tempLine.setLatLngs(ls);
        map.addLayer(tempLine);
    }
}

结束比例尺绘制并弹出设置框

function scale_end(e){
    scaleArr = [];
    scale_geometry.push(scale_line);
    map.removeLayer(tempLine);
    map.off('mousemove');
    map.off("click",addScale);
    //弹出设置框
    swal({
        title: '请输入正整数(单位为米)',
        input: 'text',
        showCancelButton: true,
        inputValidator: function (value) {
            return new Promise(function (resolve, reject) {
                let r = /^\+?[1-9][0-9]*$/;
                if (r.test(value)) {
                    resolve();
                    scale=parseInt(value);
                    scale_length = computeLength(scale_line.getLatLngs());
                } else {
                    reject('无效输入,请输入正整数!');
                }
            })
        }
    }).then(function (result) {
        scale_line.bindLabel('比例尺');
        scale_line.setText(scale+"m");

        $.each(drawnItems._layers,function (i,layer) {
            resetText(layer);
        })

        swal({
            type: 'success',
            html: '比例尺设置成功。'
        })
    }, function (dismiss) {
        for (let i = 0 ; i<scale_geometry.length; i++){
            map.removeLayer(scale_geometry[i]);
        }
    });
}

 重新绘制图形上的文本(显示面积于图形上)

function resetText(layer){
    let id = layer._leaflet_id;
    let type;
    let latlng = layer.getLatLngs();
    if (scale_length == 0){
        return;
    }
    $.each(pointAttr,function (i,item) {
        if (item.leaflet_id == id){
            type = item.type;
            return false;
        }
    });
    if (type == "polyline"){
        let distance = (computeLength(latlng) / scale_length * scale).toFixed(2);
        layer.setText(distance+"m");
    }else{
        let area = (computePolygonArea(latlng)/scale_length/scale_length*scale*scale).toFixed(2);
        layer.setText(area +"m²");
    }
}

 

 

三、下载原图及其测绘结果

由于leaflet绘图后以svg的形式展现,所以下载图片涉及到canvas转为图片、svg转为图片。

1.调整地图视野级别

leaflet绘图产生的svg图层随着地图缩放而改变矢量图形大小,可以在DOM中查看。将地图缩放到合适的视野级别,显示完整的图片图层,以便截取居于图像上的svg图层。

function download(){
    map.fitBounds(map_bound); //恢复整个图像可视范围,为了裁取居于图像上的svg图层
    setTimeout(initCanvasData,500);
}

2.绘制canvas

先获取地图底图img图片,将canvas的宽高设为其原图的宽高,之后将截取的svg图层叠加绘制于该原图上。

用到的工具:saveSvgAsPng.js

 

function initCanvasData() {
    let svgHtml =  $('svg.leaflet-zoom-animated')[0];

    //获取img图层数据,以便canvas剪切这块区域的svg
    let map_img = $("img.leaflet-image-layer.leaflet-zoom-animated");
    let map_img_width = map_img.width();
    let map_img_height = map_img.height();

    //获取svg图层数据
    let svgContainer = $("svg.leaflet-zoom-animated");
    let mapContainer = $("#image-map");
    let offset_x = svgContainer.width() - mapContainer.width();
    let offset_y = svgContainer.height() - mapContainer.height();
    console.log(offset_x,offset_y); //这里svg图层比map容器多了宽和高,下面context.drawImage时减去,否则会有偏移量
    let left = map_img.offset().left - svgContainer.offset().left,
        top =  map_img.offset().top - svgContainer.offset().top;

    //调用方法转换即可,转换结果就是uri,
    svgAsPngUri(svgHtml, null, function(uri) {
        let svg = new Image();
        svg.src=uri;
        let image = new Image();
        image.src = map_img[0].src;
        svg.onload = function(){
            if(image.complete) {
                let canvas = drawCanvas(image,svg,left-offset_x,top-offset_y,map_img_width,map_img_height);
                //下载
                downloadCanvas(canvas);
            }else{
                $(image).bind('load',function(){
                    let canvas = drawCanvas(image,svg,left-offset_x,top-offset_y,map_img_width,map_img_height);
                    //下载
                    downloadCanvas(canvas);
                }).bind('error',function(){
                    //图片加载错误,加入错误处理
                    // dfd.resolve();
                })
            }
        }
    });
}

function drawCanvas(image,svg,location_x,location_y,svg_width,svg_height){
    // let canvas = document.createElement('canvas');  //准备空画布
    let canvas = document.getElementById("downloadCanvas");  //准备空画布
    let context = canvas.getContext('2d');  //取得画布的2d绘图上下文
    canvas.width = image.width;
    canvas.height = image.height;
    context.drawImage(image,0,0);
    context.globalCompositeOperation="source-over";
    context.drawImage(svg,location_x,location_y,svg_width,svg_height,0,0,image.width,image.height);

    return canvas;
}

 

 

3.下载绘制后的canvas

function downloadCanvas(canvas){
    var link = document.createElement("a");
    var imgData =canvas.toDataURL({format: 'png', multiplier: 4});
    var strDataURI = imgData.substr(22, imgData.length);
    var blob = this.dataURLtoBlob(imgData);
    var objurl = URL.createObjectURL(blob);

    link.download = this.cName+".png";
    link.href = objurl;
    link.click();
}
//解决原图过大时下载失败问题
function dataURLtoBlob(dataurl) {
    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while(n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {type:mime});
}

 

 

 

posted @ 2021-02-12 11:31  kikiy-  阅读(542)  评论(0编辑  收藏  举报