DE-9IM predicates inspector r(geom1, geom2)

d:id:yellow_73:20080117 を拝見してこの手の話題の需要を感じたので、エントリしてみます。

DE-9IM のマトリクスを簡単にみることができるメソッドがあったら便利かも

PostGISのマニュアルを見てたら、intersectとcrossの語が使い分けられているふうにみえます。

2008-01-16

これらの言葉の定義は、OpenGIS Simple Features Specification for SQL にあります。定義はかなり数学的で、DE-9IM と呼ばれる体系を使って書かれています。
ただ、DE-9IM を理解するよりも、具体的にこれらの predicates がどのように動くかを見てみるほうが「役に立つ」のではないか、DE-9IM のマトリクスを簡単にみることができるメソッドがあったら便利かも、と思いました。

それ、geotools.rb でできるよ。

これは geotools. rb でできそうだ、と思い、irbRuby 標準の p のように使うことを想定した、グローバル関数 r を用意してみました。r は relate の r ということで。r は p の次でもあります。

# de-9im.rb
require 'geotools'

def r(g1, g2)
  g1 = Geo::import_wkt_geometry(g1) if g1.class == String
  g2 = Geo::import_wkt_geometry(g2) if g2.class == String
  m = g1.relate(g2)
  print <<-EOS
DE-9IM IntersectionMatrix = '#{m.toString}' i.e.
                            g2
               Interior  Boundary  Exterior
    Interior      #{sprintf('% 2d', m.get(0, 0))}        #{sprintf('% 2d', m.get(0, 1))}        #{sprintf('% 2d', m.get(0, 2))}
g1  Boundary      #{sprintf('% 2d', m.get(1, 0))}        #{sprintf('% 2d', m.get(1, 1))}        #{sprintf('% 2d', m.get(1, 2))}
    Exterior      #{sprintf('% 2d', m.get(2, 0))}        #{sprintf('% 2d', m.get(2, 1))}        #{sprintf('% 2d', m.get(2, 2))}

SFSQL Predicates:
  EOS
  e = %w{crosses}
  %w{equals disjoint touches within overlaps crosses intersects contains}.each do |predicate|
    v = eval("g1.#{predicate}(g2)")
    print "  #{predicate}:\t#{v}#{e.include?(predicate) ? "\t(with JTS extension)" : ''}\n"
  end
  print "\nJTS-specific Predicates:\n"
  %w{covers coveredBy}.each do |predicate|
    v = eval("g1.#{predicate}(g2)")
    print "  #{predicate}:\t#{v}\n"
  end
  nil
end

この関数を使うには、geotools.rb が必要です。デフォルト名前空間(というのかな)を汚すので、関数 r 自身は geotools.rb には含めていません。
r は幾何二つを引数に取ります。com.vividsolutions.jts.geom.Geometry のサブクラスのインスタンスでもかまいませんし、幾何の WKT 表現でもかまいません。
例外対応を全然考えていないところについてはあとで考えるかもしれません、JRuby でも CRuby + Rjb でも動くようになっていると思います。

実際に使うとこうなります

上記のスクリプトde-9im.rb という名前で Ruby が探せるところに保存しておけば、次のようにして二つの幾何の関係を表示することができるようになります。

$ jirb -rde-9im
DEBUG: jruby mode
irb(main):001:0> r 'POINT (0 0)', 'POINT (0 0)'
DE-9IM IntersectionMatrix = '0FFFFFFF2' i.e.
                            g2
               Interior  Boundary  Exterior
    Interior       0        -1        -1
g1  Boundary      -1        -1        -1
    Exterior      -1        -1         2

SFSQL Predicates:
  equals:	false
  disjoint:	false
  touches:	false
  within:	true
  overlaps:	false
  crosses:	false	(with JTS extension)
  intersects:	true
  contains:	true

JTS-specific Predicates:
  covers:	true
  coveredBy:	true
=> nil

CRuby (1.8.6) + Rjb (1.0.11) でも動きます:

$ irb -rde-9im
DEBUG: rjb primitive_conversion mode
irb(main):001:0> r 'POINT (0 0)', 'POINT (0 0)'
DE-9IM IntersectionMatrix = '0FFFFFFF2' i.e.
                            g2
               Interior  Boundary  Exterior
    Interior       0        -1        -1
g1  Boundary      -1        -1        -1
    Exterior      -1        -1         2

SFSQL Predicates:
  equals:	true
  disjoint:	false
  touches:	false
  within:	true
  overlaps:	false
  crosses:	false	(with JTS extension)
  intersects:	true
  contains:	true

JTS-specific Predicates:
  covers:	true
  coveredBy:	true
=> nil

d:id:yellow_73:20080117 の例を r してみる。

d:id:yellow_73:20080117 にある 5 つの例題を r に与えてみると、次のような結果が得られます。

irb(main):001:0> r 'POLYGON( (1 2, 1 -2, -1 -2, -1 2, 1 2) )', 'POLYGON( (2 1, 2 -1, -2 -1, -2 1, 2 1) )'
DE-9IM IntersectionMatrix = '212101212' i.e.
                            g2
               Interior  Boundary  Exterior
    Interior       2         1         2
g1  Boundary       1         0         1
    Exterior       2         1         2

SFSQL Predicates:
  equals:	false
  disjoint:	false
  touches:	false
  within:	false
  overlaps:	true
  crosses:	false	(with JTS extension)
  intersects:	true
  contains:	false

JTS-specific Predicates:
  covers:	false
  coveredBy:	false
=> nil
irb(main):002:0> r 'LINESTRING(-2 0, 2 0)', 'POLYGON( (1 1, 1 -1, -1 -1, -1 1, 1 1) )'
DE-9IM IntersectionMatrix = '101FF0212' i.e.
                            g2
               Interior  Boundary  Exterior
    Interior       1         0         1
g1  Boundary      -1        -1         0
    Exterior       2         1         2

SFSQL Predicates:
  equals:	false
  disjoint:	false
  touches:	false
  within:	false
  overlaps:	false
  crosses:	true	(with JTS extension)
  intersects:	true
  contains:	false

JTS-specific Predicates:
  covers:	false
  coveredBy:	false
=> nil
irb(main):003:0> r 'LINESTRING(0 0, 2 0)', 'POLYGON( (1 1, 1 -1, -1 -1, -1 1, 1 1) )'
DE-9IM IntersectionMatrix = '1010F0212' i.e.
                            g2
               Interior  Boundary  Exterior
    Interior       1         0         1
g1  Boundary       0        -1         0
    Exterior       2         1         2

SFSQL Predicates:
  equals:	false
  disjoint:	false
  touches:	false
  within:	false
  overlaps:	false
  crosses:	true	(with JTS extension)
  intersects:	true
  contains:	false

JTS-specific Predicates:
  covers:	false
  coveredBy:	false
=> nil
irb(main):004:0> r 'LINESTRING(1 1, -1 -1)', 'LINESTRING(-1 1, 1 -1)'
DE-9IM IntersectionMatrix = '0F1FF0102' i.e.
                            g2
               Interior  Boundary  Exterior
    Interior       0        -1         1
g1  Boundary      -1        -1         0
    Exterior       1         0         2

SFSQL Predicates:
  equals:	false
  disjoint:	false
  touches:	false
  within:	false
  overlaps:	false
  crosses:	true	(with JTS extension)
  intersects:	true
  contains:	false

JTS-specific Predicates:
  covers:	false
  coveredBy:	false
=> nil
irb(main):005:0> r 'LINESTRING(1 1, -1 -1)', 'LINESTRING(2 2, -1 -1)'
DE-9IM IntersectionMatrix = '1FF00F102' i.e.
                            g2
               Interior  Boundary  Exterior
    Interior       1        -1        -1
g1  Boundary       0         0        -1
    Exterior       1         0         2

SFSQL Predicates:
  equals:	false
  disjoint:	false
  touches:	false
  within:	true
  overlaps:	false
  crosses:	false	(with JTS extension)
  intersects:	true
  contains:	false

JTS-specific Predicates:
  covers:	false
  coveredBy:	true
=> nil

geotools.rb がもたらす機心というものがあるとすれば、このエントリはまさに geotools.rb がもたらす機心に促されて書き出したものです。そういう機心を持つことができてうれしいです。