缩放多边形以使边缘对齐

14

我正在使用JavaScript画布制作一个项目,并需要将光标捕捉到距离多边形某一特定距离的位置。我已经可以将光标捕捉到多边形本身,但我需要让光标离多边形更远一些。

据我所知,解决这个问题的最佳方法是缩放多边形并进行捕捉,但当我缩放多边形时,旧多边形和新多边形之间的边的距离并不总是匹配的。

这里是问题的示例:

enter image description here enter image description here

编辑:灰色代表原始多边形,红色代表如果我正常缩放多边形会得到什么结果,绿色则是我想要实现的

我已经尝试将多边形转化为原点,并乘以缩放因子,但似乎无法按特定距离缩放每条边。


你能发布一下你用于多边形、平移和缩放的代码吗?“不总是匹配”是什么意思?在什么情况下会失败? - K Z
1
如果你有一个多边形,并想通过创建一条新的边界来创建一个多边形,该边界基于多边形中任何线段上距离某些测量单位x的点集,忽略角点,并画线或擦除线直到你拥有一个包围原始多边形的形状时,通常不会产生与原始多边形比例相同的多边形(除非它是正多边形)。考虑一个大约为90个单位乘1个单位的细长矩形,然后在每一侧加上500,000个单位...你最终将得到一个几乎是正方形的图形。 - JayC
5个回答

10
我制作了一个jsFiddle,针对给定的多边形,计算出一个外多边形,我希望它符合您的要求。我已经在这个pdf文档中放置了背后的数学知识。
更新:代码已经被修改以处理垂直线。
function Vector2(x, y)
{
    this.x = x;
    this.y = y;
}

function straight_skeleton(poly, spacing)
{
    // https://dev59.com/mmct5IYBdhLWcg3wn-1z#11970006
    // Accompanying Fiddle: http://jsfiddle.net/vqKvM/35/

    var resulting_path = [];
    var N = poly.length;
    var mi, mi1, li, li1, ri, ri1, si, si1, Xi1, Yi1;
    for(var i = 0; i < N; i++)
    {
        mi = (poly[(i+1) % N].y - poly[i].y)/(poly[(i+1) % N].x - poly[i].x);
        mi1 = (poly[(i+2) % N].y - poly[(i+1) % N].y)/(poly[(i+2) % N].x - poly[(i+1) % N].x);
        li = Math.sqrt((poly[(i+1) % N].x - poly[i].x)*(poly[(i+1) % N].x - poly[i].x)+(poly[(i+1) % N].y - poly[i].y)*(poly[(i+1) % N].y - poly[i].y));
        li1 = Math.sqrt((poly[(i+2) % N].x - poly[(i+1) % N].x)*(poly[(i+2) % N].x - poly[(i+1) % N].x)+(poly[(i+2) % N].y - poly[(i+1) % N].y)*(poly[(i+2) % N].y - poly[(i+1) % N].y));
        ri = poly[i].x+spacing*(poly[(i+1) % N].y - poly[i].y)/li;
        ri1 = poly[(i+1) % N].x+spacing*(poly[(i+2) % N].y - poly[(i+1) % N].y)/li1;
        si = poly[i].y-spacing*(poly[(i+1) % N].x - poly[i].x)/li;
        si1 = poly[(i+1) % N].y-spacing*(poly[(i+2) % N].x - poly[(i+1) % N].x)/li1;
        Xi1 = (mi1*ri1-mi*ri+si-si1)/(mi1-mi);
        Yi1 = (mi*mi1*(ri1-ri)+mi1*si-mi*si1)/(mi1-mi);
        // Correction for vertical lines
        if(poly[(i+1) % N].x - poly[i % N].x==0)
        {
            Xi1 = poly[(i+1) % N].x + spacing*(poly[(i+1) % N].y - poly[i % N].y)/Math.abs(poly[(i+1) % N].y - poly[i % N].y);
            Yi1 = mi1*Xi1 - mi1*ri1 + si1;
        }
        if(poly[(i+2) % N].x - poly[(i+1) % N].x==0 )
        {
            Xi1 = poly[(i+2) % N].x + spacing*(poly[(i+2) % N].y - poly[(i+1) % N].y)/Math.abs(poly[(i+2) % N].y - poly[(i+1) % N].y);
            Yi1 = mi*Xi1 - mi*ri + si;
        }

        //console.log("mi:", mi, "mi1:", mi1, "li:", li, "li1:", li1);
        //console.log("ri:", ri, "ri1:", ri1, "si:", si, "si1:", si1, "Xi1:", Xi1, "Yi1:", Yi1);

        resulting_path.push({
            x: Xi1,
            y: Yi1
        });
    }

    return resulting_path;
}


var canvas = document.getElementById("Canvas");
var ctx = canvas.getContext("2d");

var poly = [
    new Vector2(150, 170),
    new Vector2(400, 120),
    new Vector2(200, 270),
    new Vector2(350, 400),
    new Vector2(210, 470)
];

draw(poly);
draw(straight_skeleton(poly, 10));

function draw(p) {
    ctx.beginPath();
    ctx.moveTo(p[0].x, p[0].y);
    for(var i = 1; i < p.length; i++)
    {
        ctx.lineTo(p[i].x, p[i].y);
    }
    ctx.strokeStyle = "#000000";
    ctx.closePath();
    ctx.stroke();
}

一个多边形被放在点对象数组中。

函数draw(p)在画布上绘制多边形p

给定的多边形在数组poly中,外部在数组poly中。

spacing是多边形之间的距离(如绿色图表中的箭头所示)。


根据Angus Johnson的评论,我创建了更多的代码片段以展示他提出的问题。这个问题比我最初想象的要困难得多。


1
我已经快速查看了你的代码,我认为它只能正确地偏移最简单的多边形。例如,我看不到任何管理非常锐角凸多边形的代码,其中顶点可以相对于偏移距离移动指数距离。此外,在具有相对较短边的锐角凹多边形中,需要有代码以消除由简单偏移导致的自交。 - Angus Johnson
@Angus Johnson,我刚按照你所指定的条件尝试了一下,我明白你的意思了。感谢你指出这一点。我会再考虑一下的。也许davey555能够评论一下这些情况是否可能在他的应用程序中出现。 - jing3142

7
我使用JavaScript编写了Clipper的端口,你可以按照自己的方式进行缩放。以下是一个膨胀多边形的示例: enter image description here 请查看Javascript Clipper的LIVE DEMO
https://sourceforge.net/projects/jsclipper/获取clipper.js文件。
这里有一个完整的代码示例,演示如何在HTML5画布上绘制偏移的多边形。
如果需要,也可以进行相反的操作(缩小),如下图所示: Offsetting polygons

3

有没有在Javascript中进行多边形偏移的想法? - Timo Kähkönen
1
Java拓扑套件JTS(Java Topology Suite)可以进行偏移操作(尽管在那里称为缓冲)。请参见https://sourceforge.net/projects/jts-topo-suite/。 - Angus Johnson
Clipper在上述测试中表现良好。似乎只有boost.geometry在某些情况下更快。我对吗,Clipper许可证允许移植到Javascript? - Timo Kähkönen
我在 http://www.angusj.com/delphi/clipper.php 上的 AGG 示例中找不到测试多边形,例如大不列颠和螺旋等。我已经制作了一个 JavaScript 端口,并希望使用与您示例中相同的数据进行测试。 - Timo Kähkönen
再次问候你,Timo。是的,可以仅使用Clipper库进行偏移操作。如果您有关于Clipper的任何进一步问题,请在SourceForge(http://sourceforge.net/p/polyclipping/discussion/)上提问,因为这不是讨论此类问题的地方。 - Angus Johnson
显示剩余6条评论

2

一种方法是找到多边形每条边与光标点之间的距离,并保留最小值。

要计算点与线段之间的距离,请将点投影到支持线上;如果投影落在端点之间,则解决方案是点到线的距离;否则,解决方案是到最近端点的距离。

这可以使用向量微积分轻松计算。


1
我可以使用JSTS库(JTS的JavaScript版本)提供解决方案。该库具有膨胀/偏移多边形的方法。
如果您想要获取具有不同类型边缘的膨胀多边形,可以设置端点和连接样式。您需要做的唯一一件事就是将多边形坐标转换为JSTS坐标,这非常简单: function vectorCoordinates2JTS (polygon) { var coordinates = []; for (var i = 0; i < polygon.length; i++) { coordinates.push(new jsts.geom.Coordinate(polygon[i].x, polygon[i].y)); } return coordinates; } 一旦您转换了坐标,就可以膨胀多边形:
function inflatePolygon(poly, spacing) {
  var geoInput = vectorCoordinates2JTS(poly);
  geoInput.push(geoInput[0]);

  var geometryFactory = new jsts.geom.GeometryFactory();

  var shell = geometryFactory.createPolygon(geoInput);
  var polygon = shell.buffer(spacing);
  //try with different cap style
  //var polygon = shell.buffer(spacing, jsts.operation.buffer.BufferParameters.CAP_FLAT);

  var inflatedCoordinates = [];
  var oCoordinates;
  oCoordinates = polygon.shell.points.coordinates;

  for (i = 0; i < oCoordinates.length; i++) {
    var oItem;
    oItem = oCoordinates[i];
    inflatedCoordinates.push(new Vector2(Math.ceil(oItem.x), Math.ceil(oItem.y)));
  }
  return inflatedCoordinates;
}

使用我在jsFiddle上发布的代码,您将得到类似于以下内容的东西:

enter image description here

附加信息: 我通常使用这种类型的膨胀/收缩(稍作修改)来设置在地图上绘制的多边形的半径边界(使用Leaflet或Google maps)。您只需将纬度、经度对转换为JSTS坐标,其余部分与以往相同。例如:

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接