今回から平面走査法という手法による、線分の交差検出について解説します。この手法を使うことで、多数の線分の中から交点を高速に見つけ出すことができます。
はじめに
計算幾何学では、巨大な入力データを扱う可能性を想定して、計算量の少ない効率的なアルゴリズムの追求が行われます。今回からは、線分の集合から交差を検出する問題を取り上げます。平面走査法と呼ばれるアルゴリズムを使用することで、総当たり式の実装に比べ、計算量を大きく削減できることを見ていきます。
多数の線分から交差を見つける
前回、線分を表すLineSegmentクラスを作成し、2つの線分が交差するかどうかを調べるintersects()メソッドや、交点座標を求めるgetIntersectionPoint()メソッドを実装しました。
さて今後、プログラムの扱う線分が100個、1,000個、あるいはそれ以上に増えたときのことを考えてみましょう。これら多数の線分の中から交点を見つけ出すには、どうすれば良いでしょうか。それが今回のテーマです。
線分の交差を示すクラス
まず初めに、線分の交差を表現するクラスを作成しておきましょう。クラス名はIntersectionとし、交差する2つのLineSegmentオブジェクト、segment1とsegment2をフィールドに持たせます。
さて、今までLineクラスやLineSegmentクラスでは省略してきたequals()メソッドとhashCode()メソッドを、将来のプログラムの都合上、Intersectionクラスでは実装することにします。この2つは、オブジェクトの同値性判定に用いられる、きわめて重要なメソッドです。同値性とequals()、hashCode()の詳細については、[こちらの解説]などを参照してください。
ここでは、線分AとBからなるIntersectionオブジェクトと、線分BとAからなるIntersectionオブジェクトを同値とみなすことにします。すなわち、segment1フィールドとsegment2フィールドを交換しても同値性が保たれるように、equals()メソッドとhashCode()メソッドをオーバーライドします。実装は以下のようになります。
交差検出のインターフェース
次に、線分の交差検出を行うIntersectionDetectorインターフェースを作成しておきます。このインターフェースは、線分のリストを入力とし、その中から交差を見つけ、Intersectionオブジェクトのコレクションとして返すexecute()メソッドを持ちます。
総当たり法による交差検出
では、IntersectionDetectorインターフェースをどのように実装すれば良いか考えてみましょう。最も単純な答えは、総当たり(brute-force)方式です。すなわち、線分A、B、C…が与えられたら、AとB、AとC、BとC…というように、すべての線分の組み合わせを列挙して、それぞれ交差判定を行う方法です。
この総当たり法を実装したものが、以下のBruteForceIntersectionDetectorクラスです。
コードを見ると分かるように、入力の線分の個数をnとすると、n回のループが実行され、それらの中でさらにn-1回、n-2回…のループが実行されるため、全体として、
回の交差判定が行われることになります。この計算量は
となります。
線分の数が少ないうちは、総当たり法でもまったく問題ありません。しかし、線分が数万、数十万の規模になると、莫大な計算量が必要になり、総当たり法は現実的でなくなってきます。
そんなに多くの線分を扱うケースなんてあるの?と思うかもしれません。しかし例えば、Blogopolisのデータベースにはおよそ70万個の多角形データが格納されており、多角形の平均角数を4とすれば、約300万個の線分を扱っていることになります。
さらに、日本や世界の国土情報を扱う地理情報システム(GIS)を考えると、Blogopolisとは桁違いの座標データを保持していることが容易に想像できます。計算幾何学では、こうした環境でも現実的な時間で計算を実行できるアルゴリズムが求められるのです。
平面走査法
総当たり法のどこに無駄があるのか、図2を例に考えてみましょう。図2では、線分s1とs2に対して、s3, s4, s5が大きく下方に離れています。直感的にいって、s3とs4は近くにあるため、交差を判定する価値がありそうですが、s1とs3の交差を判定するのは(両者が明らかに離れているため)無駄に思えます。
こうした線分同士の「近さ」を考慮に入れて、計算を効率化するにはどうすれば良いのでしょうか。ここで登場するのが、平面走査法(plane sweep algorithm)という考え方です。平面走査法では、x軸に平行な直線を走査線(sweep line)として仮想的に設定し、少しずつ下へと動かしていきます(図3)。
平面走査法では、現在の走査線と交わる線分だけに注目します。たとえば、図3で走査線が(A)の位置にあるときには、s1とs2の交差のみをチェックします。その後走査線が下に移動し、(B)の位置まで来たときには、s3とs4の交差のみをチェックします。このように、走査線を基準にして計算範囲を限定することで、計算量を大きく削減できる可能性が生まれるのです。
平面走査法の具体的な実装については、次回に譲ることにしましょう。
まとめと次回予告
今回は、線分の集合から交差を見つけるアルゴリズムについて考察しました。線分の交差を表現するクラスと交差検出のインターフェースを用意し、総当たり法による実装を行いました。さらに、総当たり法の計算量を改善するための考え方として、平面走査法の概念を紹介しました。
今回作成したプログラムのソースコードがダウンロードできます。
次回は、平面走査法の詳細に立ち入っていきます。お楽しみに!