canvas绘制可缩放的室内地图和路径

最近一直在写canvas绘制室内地图和路径这个功能,大致聊一下这个功能讲了什么。

具体是需要用canvas将室内地图绘制出来(这个不难,canvas教程有),并且地图需要能进行放大缩小,同时将地图上的路径进行展示,同样需要能放大缩小。

其实不能看出,就是将地图和路径进行同时的放大缩小。

我之前的思路是(之前没有要求缩放),创建canvas,然后设置背景(背景就是地图),然后将后端的路径数据展示到前端背景上,具体的实现,之前的博客有说明,可以翻翻看。

之后添加一个功能就是要求能将地图和路径能进行缩放,那么再将地图作为背景就不能实现缩放,所以我就使用canvas先绘制一个图片,先实现图片缩放,再在图片缩放的同时将路径数据放入到图片中,实现在绘制图片的同时,顺带加上路径数据。这是我的思路,因为没系统的接触过canvas,不知道还有没有别的思路和方法,总感觉我的还是有问题。

具体流程:(html + vue + django)

HTML<canvas id="scaleDragCanvas" width="1276" height="800"></canvas>

vue、js:
	var img_path = res.data['img_path'];  // 不用太在意,图片路径
	var piece = res.data['loc_data'];  // 不用太在意,路径数据
	_this.rountData = res.data['rount_data'];  // 不用太在意,路线数据
	
	var canvas, context;
    var showTips = null, originalPos = null;
    var initWidth = 0, initHeight = 0;
    var img, imgX = 0, imgY = 0, imgScale = 1, scaleRate = 1, scaleRatey = 1;  // imgScale 缩放比例  scaleRate 图片比例尺 imgX 图片左上角坐标
    var MINIMUM_SCALE = 1 ,pos = {}, posl = {}, dragging = false;
    var rount_len = res.data['loc_data'].length;
    
    (function int() {  // 缩放主函数
        canvas = document.getElementById('scaleDragCanvas');  // 画布对象
        context = canvas.getContext('2d');  // 画布显示二维图片

    	loadImg();
    	canvasEventsInit();  // 转换坐标

	})();
	
	
    function stroke() {  // 缩放路径
    	var color_list = ['rgb(255,0,0)',  'rgb(255,255,0)', 'rgb(0,255,0)', 'rgb(0,0,255)', 'rgb(255,0,255)', 'rgb(0,0,0)'];
    	for (var j = 0; j < rount_len; j++){
    		piece = res.data['loc_data'][j];
    		context.strokeStyle = color_list[j];
    		for (var i = 0; i < piece.length; i++) {
    			context.beginPath();
    			context.moveTo((piece[i].startX)*(imgScale),(piece[i].startY)*(imgScale));
    			context.lineTo((piece[i].endX)*(imgScale), (piece[i].endY)*(imgScale));
    			context.stroke();
    		}
    	}
    }
    
    function strokemove() {  // 移动路径
    	var color_list = ['rgb(255,0,0)',  'rgb(255,255,0)', 'rgb(0,255,0)', 'rgb(0,0,255)', 'rgb(255,0,255)', 'rgb(0,0,0)'];
    	for (var j = 0; j < rount_len; j++){
    		piece = res.data['loc_data'][j];
    		context.strokeStyle = color_list[j];
    		for (var i = 0; i < piece.length; i++) {
    			context.beginPath();
    			context.moveTo(((piece[i].startX)*(imgScale))+imgX,((piece[i].startY)*(imgScale))+imgY);
    			context.lineTo(((piece[i].endX)*(imgScale))+imgX, ((piece[i].endY)*(imgScale))+imgY);
    			context.stroke();
    		}
    	}
    }
    
    function loadImg() {  // 画图主函数
    	img = new Image();
    	img.onload = function () {
    	scaleRate = img.width / canvas.width;  // 让长宽保持相同缩放比例
    	scaleRatey = img.height / canvas.height;
    	initWidth = img.width * scaleRate;
    	initHeight = img.height * scaleRatey;

		console.log("initWidth=" + initWidth + ",initHeight=" + initHeight + ", scaleRate=" + scaleRate + ", scaleRatey=" + scaleRatey);
		drawImage();
		};
		img.src ='/media/maps/' + img_path;
	}
	
	function drawImage() {  // 画图副函数
    	context.clearRect(0, 0, canvas.width, canvas.height);  // 清空给定矩形内的指定像素

        // 保证  imgX  在  [img.width*(1-imgScale),0]   区间内
        ///**
		if(imgX<img.width*(1-imgScale) /scaleRate) {
			imgX = img.width*(1-imgScale)/scaleRate ;
		}else if(imgX>0) {
			imgX=0
		}
        // 保证  imgY   在  [img.height*(1-imgScale),0]   区间内
        if(imgY<img.height*(1-imgScale)/scaleRatey) {
			imgY = img.height*(1-imgScale)/scaleRatey;
		}else if(imgY>0) {
			imgY=0
		}
		//*/

		context.drawImage(
			img, //规定要使用的图像、画布或视频。
			0, 0, //开始剪切的 xy 坐标位置。
			initWidth, initHeight,  //被剪切图像的高度。
			imgX, imgY,//在画布上放置图像的 x 、y坐标位置。
			img.width * imgScale, img.height * imgScale  //要使用的图像的宽度、高度
		);

		stroke();
	}
	
	/*事件注册*/
    function canvasEventsInit() {
    	canvas.onmousedown = function (event) {  //  鼠标按下
        	if(showTips == null){
                dragging = true;
                pos = windowToCanvas(event.clientX, event.clientY);  // 坐标转换,将窗口坐标转换成canvas的坐标
            }
        };
       canvas.onmousemove = function (evt) {  // 鼠标移动
            if(showTips != null){
                // 测距功能
                if(showTips){
            		showPosTips(originalPos.x, originalPos.y, evt);
            	}
			}else{

                if(dragging){
                	posl = windowToCanvas(evt.clientX, evt.clientY);

					let x = posl.x - pos.x, y = posl.y - pos.y;
                    imgX  += x;
                    imgY  += y;

					pos = JSON.parse(JSON.stringify(posl));

					context.clearRect(0, 0, canvas.width, canvas.height);  // 清空给定矩形内的指定像素

                    // 保证  imgX  在  [img.width*(1-imgScale),0]   区间内
                    ///**
                    if(imgX<img.width*(1-imgScale) /scaleRate) {
						imgX = img.width*(1-imgScale)/scaleRate ;
					}else if(imgX>0) {
						imgX=0
					}
                    // 保证  imgY   在  [img.height*(1-imgScale),0]   区间内
                    if(imgY<img.height*(1-imgScale)/scaleRatey) {
						imgY = img.height*(1-imgScale)/scaleRatey;
					}else if(imgY>0) {
                    	imgY=0
                    }
					//*/

					context.drawImage(
                        img, //规定要使用的图像、画布或视频。
                        0, 0, //开始剪切的 xy 坐标位置。
                        initWidth, initHeight,  //被剪切图像的高度。
                        imgX, imgY,//在画布上放置图像的 x 、y坐标位置。
                        img.width * imgScale, img.height * imgScale  //要使用的图像的宽度、高度
					);
					strokemove();

                }else{
					let pos3 = windowToCanvas(evt.clientX, evt.clientY);
					if(context.isPointInPath(pos3.x, pos3.y)){
						console.log("进入区域");
					}
				}
			}
		};
		canvas.onmouseup = function (evt) {  // 鼠标抬起
            if(showTips != null){
				if(!showTips){
					showTips = true;
				}else{
					showTips = null;
				}
			}else{
				dragging = false;
			}
		};
		canvas.onmousewheel = canvas.onwheel = function (event) {    //滚轮放大缩小
			let pos = windowToCanvas (event.clientX, event.clientY);
			event.wheelDelta = event.wheelDelta ? event.wheelDelta : (event.deltalY * (-40));  //获取当前鼠标的滚动情况
            let newPos = {x:((pos.x-imgX)/imgScale).toFixed(2) , y:((pos.y-imgY)/imgScale).toFixed(2)};
			if (event.wheelDelta > 0) {// 放大
                imgScale +=0.1;
                imgX = 0;
                imgY = 0;
			} else {//  缩小
                imgScale -=0.1;
                if(imgScale<MINIMUM_SCALE) {//最小缩放1
				imgScale = MINIMUM_SCALE;
				}
                imgX = 0;
                imgY = 0;
			}
			drawImage();   //重新绘制图片
		};
		canvas.addEventListener('dblclick', function (event) {  // 双击
            let pos = windowToCanvas(event.clientX,event.clientY);
            //实际点的坐标
            let isX = 0;
            let isY = 0;
            //实际选中坐标 肯定 = 点击的坐标 + canvas的坐标(必须为-数)
            let temp_canvas_x = imgX-imgX*2;
            let temp_canvas_y = imgY-imgY*2;

            scaleRate = img.width / canvas.width;  // 让长宽保持相同缩放比例
            scaleRatey = img.height / canvas.height;

			if(imgScale === 1){ //原始尺寸
                isX = (pos.x+temp_canvas_x)*scaleRate;
                isY = (pos.y+temp_canvas_y)*scaleRatey;
				axios.get('/api/loc/', {params: {'loc_x': isX, 'loc_y': isY}})
			} else {
				if (imgScale > 1) { //被放大了,实际坐标需要/
                    isX = ((pos.x + temp_canvas_x) / imgScale)*scaleRate;
                    isY = ((pos.y + temp_canvas_y) / imgScale)*scaleRatey;
                    console.log(isX, isY);
                    axios.get('/api/loc/', {params: {'loc_x': isX, 'loc_y': isY}})
				} else { //被缩小了,坐标点放大
                    isX = ((pos.x + temp_canvas_x) * (1 / imgScale))*scaleRate;
                    isY = ((pos.y + temp_canvas_y) * (1 / imgScale)*scaleRatey);
                    console.log(isX, isY);
                    axios.get('/api/loc/', {params: {'loc_x': isX, 'loc_y': isY}})
				}
			}
		});

	}

	function showPosTips(x, y, evt){
        drawImage();
            context.beginPath();
            context.stokeStyle="#f00";
            context.moveTo(x, y);
            let temp = windowToCanvas(evt.clientX, evt.clientY);
            context.lineTo(temp.x, temp.y);
            context.stroke();
            document.getElementById("mouseTip").innerHTML = temp.x + "," + temp.y;
            document.getElementById("mouseTip").style.left = evt.clientX + 'px';
            document.getElementById("mouseTip").style.top = evt.clientY + 'px';
		}

	/*坐标转换*/
	function windowToCanvas(x,y) {
    var box = canvas.getBoundingClientRect();
    //console.log( "x=" + (x - box.left - (box.width - canvas.width) / 2) + ",y=" + (y - box.top - (box.height - canvas.height) / 2));
    //这个方法返回一个矩形对象,包含四个属性:left、top、right和bottom。分别表示元素各边与页面上边和左边的距离
    return {
        x: x - box.left - (box.width - canvas.width) / 2,
        y: y - box.top - (box.height - canvas.height) / 2
	};
}
django:
# api
@csrf_exempt
def get_loc(request):
    """保存地图路径到数据库"""

    json_str = request.body.decode()
    req_data = json.loads(json_str)

    req_data = req_data['loc_data']   # 一个大的列表

    req_len = len(req_data)
    for i in range(req_len):
        map_name = ((req_data[i]['map_name']).split('.')[0]) + '.jpg'
        rount_index = req_data[i]['rount_index']
        loc = req_data[i]['loc']   # 一个列表
        loc_len = len(loc)
        for i in range(loc_len):
            loc_seq = loc[i]['loc_seq']
            loc_x = loc[i]['loc_x']
            loc_y = loc[i]['loc_y']
            try:
                map = Map.objects.get(name=map_name)
                Location.objects.create(loc2_index=rount_index, loc2_x=loc_x, loc2_y=loc_y, loc2_seq=loc_seq, mid_id=map.id)
            except Exception as e:
                print(e)
                return HttpResponse('not ok')

    return HttpResponse('is ok!!!')


# api
@csrf_exempt
def get_map(request):
    """保存地图"""

    car_index = request.POST.get('car_index')
    img = request.FILES.get('img')
    map_name = request.POST.get('map_name')

    if img is None:
        return HttpResponse('img is not ok')

    try:
        robot = Robot.objects.get(car_index=car_index)
        Map.objects.create(name=map_name, rid_id=robot.id, img=img)
    except Exception as e:
        print(e)

        return HttpResponse('mysql not ok')

    new_img_path, new_img_name = pgm_tojpg(map_name)
    Map.objects.filter(name=map_name).update(name=new_img_name, img=new_img_path)

    return HttpResponse('is ok!!!')


def pgm_tojpg(img_name):
    """pgm转换成jpg"""

    img = Image.open(MEDIA_ROOT + '/maps/' + img_name)
    img_name = img_name.split('.')[0]
    new_img_name = img_name + '.jpg'
    new_img_path = 'maps/' + img_name + '.jpg'
    img.save(MEDIA_ROOT + '/maps/' + img_name + '.jpg')

    return new_img_path, new_img_name


def get_mapdetail(request):
    """获取地图详情: 坐标,路线"""

    map_name = request.GET.get('map_name')

    m = Map.objects.get(name=map_name)

    img = Image.open(MEDIA_ROOT + '/maps/' + map_name)
    wimg = img.width
    himg = img.height

    rount = m.location_set.values('loc2_index').distinct()
    list3 = []
    color_list = [{"rgb(255,0,0)": "红"},  {"rgb(255,255,0)": "黄"}, {"rgb(0,255,0)": "青"}, {"rgb(0,0,255)": "蓝"}, {"rgb(255,0,255)": "紫"}, {"rgb(0,0,0)": "黑"}]
    for r in rount:
        list3.append({
            'rount_index': (r['loc2_index']).split('rount_')[1],
            'rount_name': r['loc2_index'],
            'rount_color': list(color_list[int((r['loc2_index']).split('rount_')[1])-1].values())[0]
        })
    print(list3)
    rount_count = rount.count()  # 多少条路线
    context = {}
    list1 = []
    for i in range(rount_count):

        list2 = []
        rount_index = 'rount_' + str(i+1)
        L = m.location_set.filter(loc2_index=rount_index)

        l_len = len(L)
        if l_len <= 1:
            print('只有一个点')
        if wimg >= 1276 and himg >= 800:
            for i in range(l_len - 1):
                list2.append({
                    'startX': float(L[i].loc2_x) / (wimg / 1276),  # 5083  6597
                    'startY': float(L[i].loc2_y) / (himg / 800),
                    'endX': float(L[i + 1].loc2_x) / (wimg / 1276),
                    'endY': float(L[i + 1].loc2_y) / (himg / 800)
                })
            list1.append(list2)
        else:
            for i in range(l_len - 1):
                list2.append({
                    'startX': float(L[i].loc2_x) * (wimg / 1276),
                    'startY': float(L[i].loc2_y) * (himg / 800),
                    'endX': float(L[i + 1].loc2_x) * (wimg / 1276),
                    'endY': float(L[i + 1].loc2_y) * (himg / 800)
                })
            list1.append(list2)

    context['loc_data'] = list1
    context['img_path'] = map_name
    context['rount_data'] = list3

    return JsonResponse(data=context)


# api
@csrf_exempt
def set_loc(request):
    """传递坐标给小车"""

    loc_x = request.GET.get('loc_x')
    loc_y = request.GET.get('loc_y')
    context = {}

    context['loc_x'] = loc_x
    context['loc_y'] = loc_y
    print(context)

    res = upload_rount(context)

    return JsonResponse(res, safe=False)


# api
@csrf_exempt
def set_rount(request):
    """传递路线"""

    rount_index = request.GET.get('rount_index')
    res = upload_rount(rount_index)

    return JsonResponse(res, safe=False)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值