31

ランダムに分散された2Dポイントのセットがあります。これらのポイントの小さなサブセットに対して時間のかかる操作を実行する必要がありますが、最初に、この時間のかかる操作を実行するために必要なポイントを把握する必要があります。必要なポイントを決定するには、一連の幾何学的基準に合格する必要があります。

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

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

計算を高速化するためにKDツリーを使用して調査しようとしていますが、扇形とkdツリーに関連するアルゴリズムを見つけることができませんでした。

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

私たちの特定のシステムは浮動小数点演算と三角法の両方で遅いので、それらの少ないソリューションが多くのソリューションを必要とする優れたソリューションであることに注意してください。

4

3 に答える 3

118

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

ポイントが扇形の内側にあるためには、次のテストを満たす必要があります。

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

    ポイントがaの内側にあるためには、次のテストを満たす必要があります 最初から反時計回りに配置する必要があります セクターのエンドアームから時計回りに配置する必要があります セクターの半径よりも円の中心に近い必要があります

時計回りのテスト

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

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

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

    projection = v2.x*n1.x + v2.y*n1.y

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

反時計回りの例を次に示します。 反時計回りの例

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

手順を組み合わせることができます。

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でコードを試すことができます。

スクリーンショット

サンプルソースコード

<!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()関数を使用するなどして、最初にそれをベクトルに変換する必要があります。幸いなことに、これを行う必要があるのは1回だけです。

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

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

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

于 2012-12-03T01:02:33.467 に答える
5

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

于 2012-11-30T20:21:04.887 に答える
0

@Oren Trutnerの回答は素晴らしかったので、視覚的な例を作成し、すべての角度で機能するようにいくつかの改善を行うことにしました。

これ以上話す必要はありません。以下の例を確認してください。

$(document).on('keypress',function (e) {
        if(e.which === 13)
        {
            $("#calc").click();
        }
    });

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

    function vector(x = 0, y = 0) {
        return {x:x,y:y}
    }

    function degToRad(degree) {
        return degree * Math.PI / 180;
    }

    function isIn()
    {
        let illustration = $("#illustration");
        illustration.html("");
        let r = 250;
        let fieldOfViewAngle = 150;
        let x = Number($("#x").val());
        let y = Number($("#y").val());
        let startAngle = Number($("#startAngle").val());
        let startSectorAngle = degToRad(startAngle);
        let endSectorAngle = degToRad(startAngle+fieldOfViewAngle);

        $("#startLine").attr("x2",250 + r*Math.cos(-startSectorAngle)).attr("y2",250 + r*Math.sin(-startSectorAngle));
        $("#endLine").attr("x2",250 + r*Math.cos(-endSectorAngle)).attr("y2",250 + r*Math.sin(-endSectorAngle));
        $("#point").attr("cx",250 +x).attr("cy",250 -y);

        let sectorStartVector = vector(r * Math.cos(startSectorAngle),r * Math.sin(startSectorAngle));
        let sectorEndVector = vector(r * Math.cos(endSectorAngle),r * Math.sin(endSectorAngle));
        let relPoint = vector(x,y);

        if(!this.areClockwise(sectorStartVector, relPoint) &&
            this.areClockwise(sectorEndVector, relPoint))
            $("#result").html("Result: in");
        else{
            $("#result").html("Result: out")
        }
    }
.flixy {
            display: flex;
            flex-direction: column;
        }

        .flixy > div {
            margin-bottom: 20px;
            width:300px
        }

        .flixy > div > input {
            float: right;
        }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="result"></div>
<div class="flixy">
    <div class="input-group">
        <label>X</label>
        <input id="x">
    </div>
    <div class="input-group">
        <label>Y</label>
        <input id="y">
    </div>

    <div class="input-group">
        <label>Start angle</label>
        <input id="startAngle">
    </div>

    <div class="input-group">
        <label>Radius</label>
        <input value="250" disabled>
    </div>

    <div class="input-group">
        <label>Theta</label>
        <input value="150" disabled>
    </div>
</div>

<button onclick="isIn()" id="calc">calc</button>

<div style="width: 500px;height: 500px; overflow: visible">
    <svg width="500" height="500" style="overflow: visible">
        <circle cx="250" cy="250" r="250" stroke="black" stroke-width="3" fill="yellow"></circle>
        <line id="startLine" x1="250" y1="250" x2="500" y2="250" style="stroke:#2fa360;stroke-width:2" />
        <line id="endLine" x1="250" y1="250" x2="500" y2="250" style="stroke:#1d68a7;stroke-width:2" />
        <circle id="point" cx="250" cy="250" r="5" fill="red"></circle>
    </svg>
</div>

于 2020-05-27T20:32:27.000 に答える