点と多角形の辺との最短距離


こんにちは。
与えられた点と多角形（polygon）の辺（線分 line segment）との最短距離を求めてみました[^1]。

[^1]: ref: "[Distance from a point to a line](https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line)" (Wikipedia)）

各辺上の最短距離点を求め、その点・距離を中心・半径とする円を描いており、各円周線は与えられた点を通ります。最近傍辺が最小半径円に対応します。

<img width="300" alt="pointToPolygon.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/54617/18132e2e-5615-462a-2132-28437e1bc1f6.jpeg">


```pointToPolygon.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<div id="canvas"></div>
<script type="text/javascript">

function dotTo(a, b, dest) {
  for (var z=0, i=a.length-1; i>=0; i--) {
    z += (a[i]-dest[i])*(b[i]-dest[i])
  }
  return z;
}

function distanceSquared(a, b) {
  return dotTo(a, a, b);
}

function linearCombination(a, b, s, t) {
  for (var c=[], i=0, len=a.length; i<len; i++) {
    c.push(a[i]*s+b[i]*t);
  }
  return c;
}

function pointToPolyline(point, poly) {
  for (var i=0, z=[], len=poly.length, j=len-1; i<len; j=i++) {
    z.push(pointToLineSegment(point, poly[i], poly[j]));
  }
  return z;
}

// pointToLineSegment
//   inputs: point, pointStart_LineSegment, pointEnd_LineSegment
//   output: [foot.x, foot.y, distance**2]
function pointToLineSegment(point, pointStart, pointEnd) {
  var foot, dotToStart = dotTo(point, pointEnd, pointStart);
  if (dotToStart<=0) {
    foot = pointStart;
  } else {
    var dotToEnd = dotTo(point, pointStart, pointEnd);
    if (dotToEnd<=0) {
      foot = pointEnd;
    } else {
      var t = dotToStart/(dotToStart+dotToEnd);
      foot = linearCombination(pointStart, pointEnd, 1-t, t);
    }
  }
  return foot.concat(distanceSquared(point, foot));
}

function indexOfApply(func, arr) {
    return arr.indexOf(func.apply(null,arr));
}

function mytransform(p) {
    var scale = 24, offset = 160;
    q = [p[0]*scale+offset, (7-p[1])*scale+offset];
    if (p.length>2) {q.push(p[2]*scale)} 
    return q;
}

var point = [6,5], poly = [[0,0], [7,0], [7,1], [1,7], [0,7]];
var circles = pointToPolyline(point, poly).map(function(p){return [p[0], p[1], Math.sqrt(p[2])]});
var imin = indexOfApply(Math.min, circles.map(function(p){return p[2]}));
point = mytransform(point);
poly = poly.map(mytransform);
circles = circles.map(mytransform);

var svg = d3.select("#canvas")
    .append("svg")
    .attr("width",450)
    .attr("height",450);

svg.append("polygon")
    .data([poly])
    .attr("points", function(d){return d.join(" ")})
    .attr("stroke", "gray")
    .attr("fill", "#e0fff0")
    .attr("stroke-width", 1.5);

svg.selectAll('circle').data(circles).enter().append('circle').attr({
      'cx': function(d) {return d[0];},
      'cy': function(d) {return d[1];},
      'r': function(d) {return d[2];}
    }).attr("stroke", "orange").attr("stroke-width", "1.5").attr("fill","yellow").attr("fill-opacity", 0.04);

svg.selectAll("points")
    .data([point])
    .enter().append("circle")
    .attr("fill", "#4040ff")
    .attr("r", 3)
    .attr("transform", function(d){return `translate(${d})`});

svg.selectAll("points")
    .data(circles.map(function(d){return d.slice(0,2)}))
    .enter().append("circle")
    .attr("fill", "#b0b0b0")
    .attr("r", 2)
    .attr("transform", function(d){return `translate(${d})`});

// the minimum distance point on a polygon
svg.selectAll("points")
    .data([circles[imin].slice(0,2)])
    .enter().append("circle")
    .attr("fill", "#40a040")
    .attr("r", 2.5)
    .attr("transform", function(d){return `translate(${d})`});

</script>
</body>
</html>
```