web-dev-qa-db-ja.com

円セクター内のポイントを効率的に見つける

ランダムに配布された2Dポイントのセットがあります。これらのポイントの小さなサブセットで時間のかかる操作を実行する必要がありますが、この時間のかかる操作を実行するために必要なポイントを最初に把握する必要があります。どの点が必要かを判断するには、一連の幾何学的基準を通過する必要があります。

最も基本的な基準は、特定のポイントから特定の距離内にあることです。 2番目に最も基本的な基準は、それらがその特定のポイントから伸びる扇形(2次元円錐)に含まれているかどうかです。 (編集:この操作は、毎回異なる特定のポイントで定期的に呼び出されますが、同じ2Dポイントのセットです。)

私の最初の考えは、2Dポイントを含むグリッドを作成し、それが交差するコーングラビンググリッドの正方形に沿って反復することでした。グリッドのサイズに応じて、不要な2Dポイントの大部分を除外します。残念ながら、私が実行している組み込みシステムはメモリに厳しく制限されているため、(私たちの基準では他の誰でもない)大きな2Dアレイはメモリを大量に消費します。

計算を高速化するためにKDツリーを使用して調査を試みましたが、円セクターとkdツリーに関連するアルゴリズムを見つけることができませんでした。

円セクター内にある2Dポイントを見つけるための効率的なアルゴリズムはありますか?

特筆すべきは、私たちの特定のシステムは浮動小数点演算と三角法の両方で低速であるため、それらをあまり含まないソリューションは、それを多く必要とする優れたソリューションです。

26
ApockofFork

整数演算と、加算、減算、乗算の基本演算のみを使用して、ポイントがセクター内にあるかどうかを確認できます。

ポイントが 円形セクター の内側になるには、次のテストを満たす必要があります。

  1. セクターの開始「アーム」から反時計回りに配置する必要があります。
  2. セクターのエンドアームから時計回りに配置する必要があります。
  3. セクターの半径よりも円の中心に近い必要があります。

    For a point to be inside a, it has to meet the following testsIt has to be positioned counter-clockwise from the start "arm" of the sectorIt has to be positioned clockwise from the end arm of the sectorIt has to be closer to the center of the circle than the sector's radius

時計回りのテスト

ベクトルv2が別のベクトルv1に対して時計回りであるかどうかをテストするには、次のようにします。

  1. V1の反時計回りの 法線ベクトル を見つけます。法線ベクトルは、元のベクトルに対して90度の角度です。これは 実行するのが簡単v1=(x1,y1)の場合、反時計回りの法線はn1=(-y1,x1)です。

  2. 法線上のv2の投影のサイズを見つけます。これは、v2の dot product と法線を計算することで実行できます。

    _projection = v2.x*n1.x + v2.y*n1.y_

  3. 投影が正の数の場合、v2はv1に対して反時計回りに配置されます。それ以外の場合、v2はv1に対して時計回りです。

ここに反時計回りの例があります: Counter-clockwise example

そして時計回りの例: Clockwise example

ステップは組み合わせることができます:

_function areClockwise(v1, v2) {
  return -v1.x*v2.y + v1.y*v2.x > 0;
}
_

半径テスト

半径テストは簡単です。円の中心からのポイントの距離が希望の半径よりも小さいかどうかを確認してください。平方根の計算を回避するために、代わりに距離の2乗と半径の2乗を比較できます。

_function isWithinRadius(v, radiusSquared) {
  return v.x*v.x + v.y*v.y <= radiusSquared;
}
_

それを一緒に入れて

完全なセクターテストは次のようになります。

_function isInsideSector(point, center, sectorStart, sectorEnd, radiusSquared) {
  var relPoint = {
    x: point.x - center.x,
    y: point.y - center.y
  };

  return !areClockwise(sectorStart, relPoint) &&
         areClockwise(sectorEnd, relPoint) &&
         isWithinRadius(relPoint, radiusSquared);
}
_

次のサンプルページは、これを数千点以上示しています。 http://jsbin.com/oriyes/8/edit でコードを試すことができます。

Screenshot

サンプルソースコード

_<!DOCTYPE html>
<html>
  <head>
    <script src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
    <style>
      .canvas {
        position: absolute;
        background: #f4f4f4;
        border: 8px solid #f4f4f4;
        width: 400px;
        height: 400px;
      }

      .dot {
        position: absolute;
        font: 16px Arial;
      }
      .out { color: #ddd; }
      .in { color: #00dd44; }
    </style>
    <script>
      function isInsideSector(point, center, sectorStart, sectorEnd, radiusSquared) {
        var relPoint = {
          x: point.x - center.x,
          y: point.y - center.y
        };

        return !areClockwise(sectorStart, relPoint) &&
               areClockwise(sectorEnd, relPoint) &&
               isWithinRadius(relPoint, radiusSquared);
      }

      function areClockwise(v1, v2) {
        return -v1.x*v2.y + v1.y*v2.x > 0;
      }

      function isWithinRadius(v, radiusSquared) {
        return v.x*v.x + v.y*v.y <= radiusSquared;
      }

      $(function() {
        var $canvas = $("#canvas");
        var canvasSize = 400;
        var count = 4000;

        // define the sector
        var center = { x: canvasSize / 2, y: canvasSize / 2 };
        var sectorStart = { x: 4, y: 1 };
        var sectorEnd = { x: 1, y: 4 };
        var radiusSquared = canvasSize * canvasSize / 4;

        // create, draw and test a number of random points
        for (var i = 0; i < count; ++i) {

          // generate a random point
          var point = {
            x: Math.random() * canvasSize,
            y: Math.random() * canvasSize
          };

          // test if the point is inside the sector
          var isInside = isInsideSector(point, center, sectorStart, sectorEnd, radiusSquared);

          // draw the point
          var $point = $("<div class='dot'></div>")
              .css({
                left: point.x - 3,
                top:  canvasSize - point.y - 8 })
              .html("&#8226;")
              .addClass(isInside ? "in" : "out")
              .appendTo($canvas);
        }
      });
    </script>
  </head>
  <body>
    <div id="canvas" class="canvas"></div>
  </body>
</html>
_

注意、警告、制限

  1. ベクトルでセクターの境界を指定する必要があります。たとえば、上のスクリーンショットは、(4,1)と(1,4)のベクトルの間で伸びるセクターを示しています。

  2. セクターが他の用語で指定されている場合。角度を指定するには、まずそれをベクトルに変換する必要があります。 tan()関数を使用します。幸いなことに、これを行う必要があるのは一度だけです。

  3. ここでのロジックは、180度未満の内角を持つセクターに対して機能します。セクターがより大きな角度にまたがることができる場合は、それを変更する必要があります。

  4. さらに、コードは、セクターの境界ベクトルのどれが「開始」であり、どちらが「終了」であるかを知っていることを前提としています。そうでない場合は、areClockwise()を実行して調べることができます。

  5. これはすべて整数演算で実行できますが、xとyを2乗して乗算するため、半径と時計回りの両方のテストでより広い範囲の数値が使用されることに注意してください。結果を保持するのに十分なビットの整数を使用してください。

106
Oren Trutner

三角法が必要ないことはわかっていますが、(サブセット内の)各ポイントを極座標(原点が特定のポイント)としきい値に変換できますr,theta どこ r < RおよびT1 < theta < T2セクターに対応。それは確かにメモリ効率が良いです!

5
Jacob