带有弯头的路线或路径标记的解决方案 [英] Solution for route or path marking with bends

查看:104
本文介绍了带有弯头的路线或路径标记的解决方案的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想为我的用户提供一种简便的方法,以可视方式在地图或图片上跟踪路线.该解决方案必须让用户添加控制点,他们可以将其用于将折弯放置到路径中.

I want to give my users an easy way to visually trace a route on a map or a picture. The solution must let the users add control points that they can use to put bends into the route.

它应该与html5 canvas一起使用-我目前使用Konvajs库,因此使用此库的解决方案将是不错的选择.

It should work with html5 canvas - I currently use the Konvajs library so a solution that uses this would be good.

为了共享与分享的利益学习,如果您可以建议使用其他HTML5画布库的解决方案,那也很值得一看.

In the interests of sharing & learning, if you can suggest solutions using other HTML5 canvas libraries that would be good to see too.

注意:这不是最初提出的问题.但是随着时间的流逝,这才是实际的要求. OP要求在HTML5画布中沿着直线/曲线找到任意点的方法,以便可以在该点处添加可拖动的控制点以编辑直线/曲线.接受的答案不满足此需求.但是,对于这个原始问题的答案将涉及严重的碰撞检测数学以及可能使用贝塞尔曲线控制点的问题-换句话说,这将是一个大问题,而被接受的答案是具有一致的UX的非常容易解决的解决方案.

原始问题可以通过该问题下方的编辑链接查看.

推荐答案

这个想法怎么样.单击想要下一个点的位置,路径线会沿线段延伸新的定位手柄.如果需要箭头,可以根据需要扩展此处的对象.您可以使用路线类别的属性轻松更改颜色,笔触宽度,圆形不透明度等.这些点在数组中和标准Konva.js行点列表中可用. JS是原始版本,不需要或使用其他任何库.

How about this idea. You click where you want the next point and the route line extends with new positioning handles along the line segments. If you need arrows you can extend the objects herein as you require. You can easily change colours, stroke width, circle opacity etc with attributes of the route class. The points are available in an array and in the standard Konva.js line points list. The JS is vanilla, no other libraries needed or used.

导出"按钮显示了如何获取(x,y)定点对象以进行导出.

The Export button shows how to grab the (x,y) fixed point objects for export purposes.

此处为示例视频,以下代码段中为工作代码.

Example video here, working code in below snippet.

// Set up the canvas / stage
var s1 = new Konva.Stage({container: 'container1', width: 600, height: 300});

// Add a layer for line
var lineLayer = new Konva.Layer({draggable: false});
s1.add(lineLayer);

// Add a layer for drag points
var pointLayer = new Konva.Layer({draggable: false});
s1.add(pointLayer);

// Add a rectangle to layer to catch events. Make it semi-transparent 
var r = new Konva.Rect({x:0, y: 0,  width: 600, height: 300, fill: 'black', opacity: 0.1})
pointLayer.add(r)

// Everything is ready so draw the canvas objects set up so far.
s1.draw()

// generic canvas end



// Class for the draggable point
// Params: route = the parent object, opts = position info, doPush = should we just make it or make it AND store it
var DragPoint = function(route, opts, doPush){
  var route = route;

  this.x = opts.x;
  this.y = opts.y;
  this.fixed = opts.fixed;
  this.id = randId();  // random id.

  if (doPush){  // in some cases we want to create the pt then insert it in the run of the array and not always at the end
    route.pts.push(this);  
  }

  // random id generator
  function randId() {
     return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10);
  }

  // mark the pt as fixed - important state, shown by filled point
  this.makeFixed = function(){
    this.fixed = true;
    s1.find('#' + this.id)
        .fill(route.fillColor);      
  }
  
  this.kill = function(){
    s1.find('#' + this.id)
        .remove();        
  }
  
  this.draw = function(){
    // Add point & pt
    var circleId = this.id;
 
    var pt = new Konva.Circle({
      id: circleId,
      x: this.x, 
      y: this.y, 
      radius: route.pointRadius,
      opacity: route.pointOpacity,
      strokeWidth: 2,
      stroke: route.strokeColor,
      fill: 'transparent',
      draggable: 'true'    
    })
    pt.on('dragstart', function(){
        route.drawState = 'dragging';
    })
    pt.on('dragmove', function(){
      var pos = this.getPosition();
      route.updatePt(this.id(), pos)
      route.calc(this.id());
      route.draw();
    })
    pt.on('dragend', function(){

      route.drawState = 'drawing';
      var pos = this.getPosition();

      route.updatePt(this.getId(), pos);

      route.splitPts(this.getId());
      
      route.draw();
    })

    if (this.fixed){
      this.makeFixed();
    }
    
    
    route.ptLayer.add(pt);
    route.draw();

  }  
  
}

var Route = function() {

    this.lineLayer = null;
    this.ptLayer = null;
    this.drawState = '';

    this.fillColor = 'Gold';
    this.strokeColor = 'Gold';
    this.pointOpacity = 0.5;
    this.pointRadius = 10;
    this.color = 'LimeGreen';
    this.width = 5;
  
    this.pts = []; // array of dragging points.

    this.startPt = null;
    this.endPt = null;

    // reset the points 
    this.reset = function(){
      for (var i = 0; i < this.pts.length; i = i + 1){
        this.pts[i].kill();
      }
      this.pts.length = 0;
      this.draw();
    }

    // Add a point to the route.
    this.addPt = function(pos, isFixed){ 
      
      if (this.drawState === 'dragging'){  // do not add a new point because we were just dragging another
        return null;
      }
      
      this.startPt = this.startPt || pos;
      this.endPt = pos;

      // create this new pt
      var pt = new DragPoint(this, {x: this.endPt.x, y: this.endPt.y, fixed: isFixed}, true, "A");
      pt.draw();
      pt.makeFixed(); // always fixed for manual points
      
      // if first point ignore the splitter process
      if (this.pts.length > 0){
        this.splitPts(pt.id, true);
      }    

      this.startPt = this.endPt; // remember the last point

      this.calc(); // calculate the line points from the array
      this.draw();  // draw the line 
    }

  // Position the points.  
  this.calc = function (draggingId){
    draggingId = (typeof draggingId === 'undefined' ? '---' : draggingId); // when dragging an unfilled point we have to override its automatic positioning.

    for (var i = 1; i < this.pts.length - 1; i = i + 1){

      var d2 = this.pts[i];
      if (!d2.fixed && d2.id !== draggingId){      // points that have been split are fixed, points that have not been split are repositioned mid way along their line segment.

        var d1 = this.pts[i - 1];
        var d3 = this.pts[i + 1];
        var pos = this.getHalfwayPt(d1, d3);
        
        d2.x = pos.x;
        d2.y = pos.y;
      }
      s1.find('#' + d2.id).position({x: d2.x, y: d2.y}); // tell the shape where to go
    }
  }

  // draw the line
  this.draw = function (){  

    if (this.drawingLine){
      this.drawingLine.remove();
    }
    this.drawingLine = this.newLine(); // initial line point
    
    for (var i = 0; i < this.pts.length; i = i + 1){
      this.drawingLine.points(this.drawingLine.points().concat([this.pts[i].x, this.pts[i].y]))
    }
    
    this.ptLayer.draw();
    this.lineLayer.draw();
  }

  // When dragging we need to update the position of the point
  this.updatePt = function(id, pos){

      for (var i = 0; i < this.pts.length; i = i + 1){
        if (this.pts[i].id === id){

          this.pts[i].x = pos.x;
          this.pts[i].y = pos.y;

          break;
        }    
      }
  }

  // Function to add and return a line object. We will extend this line to give the appearance of drawing.
  this.newLine = function(){
    var line = new Konva.Line({
        stroke: this.color,
        strokeWidth: this.width,
        lineCap: 'round',
        lineJoin: 'round',
        tension : .1
      });

    this.lineLayer.add(line)
    return line;
  }  


  // make pts either side of the split
  this.splitPts = function(id, force){
    var idx = -1;
    
    // find the pt in the array
    for (var i = 0; i < this.pts.length; i = i + 1){
      if (this.pts[i].id === id){
        idx = i;

        if (this.pts[i].fixed && !force){
          return null; // we only split once.
        }

        //break;
      }   
    }

    // If idx is -1 we did not find the pt id !
    if ( idx === -1){
      return null
    }
    else if (idx === 0  ) { 
      return null
    }
    else { // pt not = 0 or max 

      // We are now going to insert a new pt either side of the one we just dragged
      var d1 = this.pts[idx - 1]; // previous pt to the dragged pt
      var d2 = this.pts[idx    ]; // the pt pt
      var d3 = this.pts[idx + 1]; // the next pt after the dragged pt

      d2.makeFixed()// flag this pt as no longer splittable

      // get point midway from prev pt and dragged pt    
      var pos = this.getHalfwayPt(d1, d2);
      var pt = new DragPoint(this, {x: pos.x, y: pos.y, foxed: false}, false, "C");
      pt.draw();
      this.pts.splice(idx, 0, pt);

      if (d3){
        // get point midway from dragged pt to next     
        pos = this.getHalfwayPt(d2, d3);
        var pt = new DragPoint(this, {x: pos.x, y: pos.y, foxed: false}, false, "D");
        pt.draw();
        this.pts.splice(idx + 2, 0, pt); // note idx + 2 !

      }

    }  

  }  
  
  // convert last point array entry to handy x,y object.
  this.getPoint = function(pts){
    return {x: pts[pts.length - 2], y: pts[pts.length - 1]};
  }
  
  this.getHalfwayPt = function(d1, d2){
    var pos = {
          x: d1.x + (d2.x - d1.x)/2, 
          y: d1.y + (d2.y - d1.y)/2
      }
    return pos;
  }

  this.exportPoints = function(){
    var list = [], pt;    
    console.log('pts=' + this.pts.length)
    for (var i = 0; i < this.pts.length; i = i + 1){      
      pt = this.pts[i]
      if (pt.fixed){
        console.log('push ' + i)
        list.push({x: pt.x, y: pt.y});   
      }   
    }  
    return list;
  }
  
}

var route = new Route();
route.lineLayer = lineLayer;
route.ptLayer = pointLayer;

route.fillColor = 'AliceBlue'; 
route.strokeColor = 'Red'; 
route.pointOpacity = 0.5;
route.pointRadius = 7;
route.color = '#2982E8'


// Listen for mouse up on the stage to know when to draw points
s1.on('mouseup touchend', function () {

  route.addPt(s1.getPointerPosition(), true);

  
  
});

// jquery is used here simply as a quick means to make the buttons work.

// Controls for points export
$('#export').on('click', function(){

  if ($(this).html() === "Hide"){
    $(this).html('Export');
    $('#points').hide();
  }
  else {
    $(this).html('Hide');
    $('#points')
      .css('display', 'block')
      .val(JSON.stringify(route.exportPoints()));
  }  

})

// reset button
$('#reset').on('click', function(){
  route.reset();
  })

p
{
  padding: 4px;
}
#container1
{
background-image: url('https://i.stack.imgur.com/gADDJ.png');
}
#ctrl
{
position: absolute;
z-index: 10;
margin: 0px;
border: 1px solid red;
}
#points
{
width: 500px;
height: 100px;
display: none;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<p>Click to add a point, click to add another, drag a point to make a bend, etc.
</p>
<div id='ctrl'>
<button id='reset'>Reset</button>
<button id='export'>Export</button>
<textarea id='points'></textarea>
</div>
<div id='container1' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>
<div id='img'></div>

这篇关于带有弯头的路线或路径标记的解决方案的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆