以编程方式绘制一组正方形边界的最佳方法是什么?

时间:2016-04-07 10:05:43

标签: javascript html5 algorithm google-maps html5-canvas

我正在构建一个工具,最终将利用Google Maps API v3在固定“网格”系统上固定边长(例如10米)的正方形构建的地图上构建一个区域(例如,纵坐标从地球的0,0点开始每隔0.0001个纬度单位间隔开。

我编写了代码,用户可以点击地图上的某个区域,代码会绘制一个轮廓并填充找到它的方块。用户可以单击该方块的其他相邻位置以构建越来越大的“块状”多边形,并可以单击各个方块以删除它们。我已经在HTML5 canvas / JavaScript以及Google Maps API中测试了所有这些。

现在我想编写删除任何内部边/顶点的代码,以便它只绘制此多边形的最外边界,以便它实际上绘制为一个大的多边形,而不是一个广场。可以这样想:即使我们知道像澳大利亚,美国等国家,由许多州组成,当我们在全国范围内划定边界时,我们通常不会对所有州的边界感兴趣,可以删除那些两者之间的线条,只绘制外边界。这与我想要完成的事情相同,只是使用正方形网格而不是复杂的多边形。

我目前的代码在这里:

https://jsfiddle.net/wyxcvmdf/14/

HTML:

<canvas id="myCanvas" width="500" height="250" style="border:1px solid #000000;"></canvas>

<!--etc.-->

JavaScript的:

// don't forget to set load type in jsfiddle to no wrap in <body>

// define the global variable and some helper variables to make the code shorter
var gv = {};
gv.o = function(id) {
  return document.getElementById(id)
};
gv.i = 'innerHTML';

// etc.

关于我的代码的几个解释性说明:

•每个正方形的“原点”是该正方形左下角的顶点。没有特别的原因。

•HTML5画布如何绘制轮廓的“绘图方向”是从原点逆时针方向。再一次,没有特别的理由。

•您无法“点击”添加方块,因为它只是一个概念验证,因此您可以通过在相关文本输入框中输入x和y坐标来添加方块

证明我的代码所需的用例/测试是:

  • 方形添加到多边形,有1个重复顶点(工作)

  • 在所有情况下,方形添加到多边形中,包含2个和3个重复顶点:相邻边,非相邻边,非连续顶点(当前仅适用于第一种情况)

  • 方形在所有情况下都添加到多边形中,有4个重复的顶点:插入一个洞,插入一部分洞,连接多个多边形(目前只适用于第一种情况)

  • 在上述情况下,从多边形中移除了一个带有1个重复顶点的方块(尚未开发,但应该有效地与加法代码“反向”)

  • 在上述情况下,从多边形中移除了方形,其中有2个和3个重复顶点(尚未开发,但应该有效地与加法代码“反向”)

  • 在上述情况下,从具有4个重复顶点的多边形中删除了正方形(尚未开发,但应该有效地与添加代码“反向”)

  • 在具有多个内边界的多边形外部方形添加/移除,即孔(尚未开发,可能很棘手)

  • 在具有多个内边框的多边形内部方形添加/移除,即孔(尚未开发,可能很棘手)

注1:我使用“square”,“edge”等代替“polygons”等,只是为了简化解释。

注2:我对类似的问题和可能的解决方案进行过相当多的研究,但还没有真正找到满足我需求的东西。我所做的研究是:

  • 旅行推销员问题。然而,这不是关于优化路径 - 它是关于确保路径是“可绘制的”并因此朝向一个方向。只要生成的形状看起来像用户期望的那样,重叠顶点就完全没了。

  • 凸壳算法。不太适用,因为船体可能是凸的,凹的,甚至是非连续的!此外,我认为通过简化网格系统,我已经消除了许多散射顶点的问题,你需要确定它们与中心点的距离,使用三角函数等。

  • 凹陷船体解决方案。这更接近于解决我的问题,但我所看到的是,有许多商业工具插件(例如ArcGIS)可以做到这一点,但是没有涵盖我所有用例的通用代码(不论编程语言)。

  • 基于平铺的游戏。您会认为任何需要在图块周围绘制边界的基于图块的游戏(例如,实时战略游戏中的玩家区域)都会解决这个问题问题,但不是我能看到的。

2 个答案:

答案 0 :(得分:1)

你说“画”而不是计算外部顶点,所以......

你可以使用剪辑加合成来“挖空”你的方块。

假设您已确定这些方块位于所需边界内(部分或完全位于内部):

var aInside=[ {x:60,y:60},{x:80,y:60},{x:40,y:60},{x:60,y:40},{x:60,y:80} ];

所需边界内的正方形图示。

enter image description here

然后,要仅绘制正方形的边界,您可以:

  1. 每个内部方块的笔划(不填充):context.rect
  2. enter image description here

    1. 将进一步的绘图限制在抚摸的地方:context.clip

    2. 导致所有新绘图擦除现有像素:context.globalCompositeOperation = 'destination-out'

    3. 使用纯色填充整个画布:context.fillRect(0,0,canvas.width,canvas.height)

    4. 诀窍:抚摸一个矩形实际上画了一个内部和一半的笔画。在矩形的一半外面,所以步骤#4将擦除矩形集的内部,但(重要的是!)将留下一半的外部笔划。

      所以你最终得到了这个:

      enter image description here

      以下是示例代码和演示:

      var canvas=document.getElementById("canvas");
      var ctx=canvas.getContext("2d");
      
      var aInside=[ {x:60,y:60},{x:80,y:60},{x:40,y:60},{x:60,y:40},{x:60,y:80} ];
      
      // stroke all inside squares
      ctx.save();
      ctx.beginPath();
      for(var i=0;i<aInside.length;i++){
          var s=aInside[i];
          ctx.rect(s.x,s.y,20,20);
      }
      ctx.stroke();
      
      // clip to cause all new drawing to be inside the stroked squares
      ctx.clip();
      
      // set compositing to use new drawings to  "erase" existing drawings
      ctx.globalCompositeOperation='destination-out';
      
      // Fill (===erase!) the entire canvas 
      // Clipping causes only the clipping area to be erased
      //     so the inside of the rects set is "hollowed out"
      ctx.fillRect(0,0,canvas.width,canvas.height);
      ctx.restore();
      body{ background-color: ivory; }
      #canvas{border:1px solid red; }
      <canvas id="canvas" width=150 height=150></canvas>

      算法注释:如果您想要一组幸存的顶点而不是绘图,则可以修改Marching Squares Algorithm以仅返回拐点。那些拐点是你的外边界的顶点。

答案 1 :(得分:1)

此方法仅处理绘图/外观 - 它不会生成任何新多边形。但它允许您使用多边形的集合(任何形状,这里是矩形)并在视觉上合并它们以产生合并的轮廓。我将此答案基于我的earlier answers之一,但修改并采用以适应此处的方案:

  • 将所有矩形绘制为实体
  • 在所有边缘和角落处重新绘制它们,以挤出到您想要的厚度
  • 重绘原始矩形,但全局复合模式设置为destination-out并居中于顶部

有几个步骤,但它的工作速度非常快。

几点说明:

  • 如果您有现有背景,则需要使用屏幕外画布作为临时舞台。此处未显示,但步骤将相同,只是您将在屏幕外的上下文中执行这些步骤,最后您将从显示画布的现有内容之上的屏幕外画布复制内容。
  • 如果您有很多矩形,可以通过将每个矩形绘制到单独的离屏画布来优化,而无需重新绘制任何其他内容。然后,当您执行下面显示的拉伸过程时,您只需使用此离屏画布作为源(例如,请参阅上面的链接,只需将屏幕外的画布替换为源代码)。
  • 可以通过检查是否嵌入矩形来进一步优化它,如果是,则将其从集合中删除。

演示

&#13;
&#13;
var ctx = c.getContext("2d"),
    rw = 50, rh = 50,                               // some demo size
    rectangles = [];                                // rectangle collection

function render(ctx) {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.fillStyle = "#a00";
  ctx.globalCompositeOperation = "source-over";     // draw using standard mode3

  // we will draw the same rects on top of each other eight times
  // this will extrude the edge so we can in the next step punch a
  // hole in the drawing and leave only the extrusion -
  // offset array (x,y) pairs
  var i, d = 2,                                // d = number of pixels to offset
      offsets = [-d, -d,  0, -d,  d, -d,  d, 0,  d, d,  0, d, -d, d, -d, 0];
  
  for(i = 0; i < offsets.length; i += 2) {
    ctx.setTransform(1,0,0,1,  offsets[i], offsets[i+1]);
    drawRects()
  }
  
  // punch hole in the center
  ctx.setTransform(1,0,0,1,0,0);                    // reset transformatons
  ctx.globalCompositeOperation = "destination-out"; // "erase" mode
  drawRects();                                      // draw a final time, wo/ extrusion
  
  function drawRects() {
    ctx.beginPath();
    rectangles.forEach(function(r) {
      ctx.rect(r.x, r.y, r.w, r.h)
    });                                             // loop through collection and draw
    ctx.fill()
  }
}

// demo stuff --
c.onclick = function(e) {
  var r = this.getBoundingClientRect(),             // for demo, get mouse position
      x = e.clientX - r.left,
      y = e.clientY - r.top;
  
  // add rectangle to list
  rectangles.push({                                 // generate a rect. from center
    x: x - rw*0.5,
    y: y - rh*0.5,
    w: rw,
    h: rh
  });
  
  render(ctx);                                      // the key process
};
&#13;
canvas {border:1px solid #000}
&#13;
Click on the canvas below to place rectangles:<br>
<canvas width=600 height=600 id=c></canvas>
&#13;
&#13;
&#13;