多角形の面積

多角形
座標点Pのn点であらわされる多角形の面積は、
多角形の面積0で計算されます。
          但し、Xn+1=X1,Yn+1=Y1
カッコの部分を書き直すと、 k+1k+1k+1k+1 となります。
-Yk+1k+1の部分のシグマの値は、ゼロになるので

外積計算
となります。(外積による計算と同じ)
左回りに座標を入れ計算するのか、右回りで計算するのかで、値がマイナスとなるので、どちら周りで計算しても良いように絶対値をとって

面積計算
外積2
プログラム上で計算するので、どちらで計算しても良いでしょう。
外積については、イメージングソリューションに詳しい説明が載っているので参照して下さい。

最初の計算式についての検討。
 面積を持つ多角形で一番座標点が少ないのは、三角形なので三角形について検討します。
座標点を結ぶ二点の直線の下側部分X軸までの、面積を計算します。
X座標の差分と、Y座標を加算した値の積を二分の一にすると、その面積が計算できます。
全ての辺について、一周計算し、面積の合計値を計算すれば、三角形部分の面積となります。
今回の例では、左回りに計算すると、面積は正の値となり、右回りに計算すると、負の値になります。
(Y軸方向に面積を計算する場合は、Y座標の差分と、X座標の加算をとります。)

面積計算検討
 面積S1の値はマイナスになるので、面積Sは、S=S1+S2+S3 となるのは、上図をみれば、あきらかです。
座標点を結ぶ直線が交差しない限り、座標点が増えても、計算式が成り立ちます。

 計算プログラム使用時の座標入力時の注意点
 座標入力
 上図の様に同じ座標を通る図形がある場合、左図の様に図形上を同じ方向に回るように入力します。
右図のように入力すると、上の三角形部と、下の三角形部で入力方向が逆方向になってしまい、正しく計算されません。
上の三角形と、下の三角形を別々に計算したほうが、間違いを防ぐことが出来るでしょう。
同じ座標点があったら、入力ミスとすれば良いのですが、此処でのプログラムでは簡単なので省略しています。

此処でダウンロード出来るサンプルプログラムでは、ある程度の座標値の入力ミスのチェックをしていますが、入力方向(左回り右回り)の確認はしていません。
入力ミスの判定は次の様におこないます。
データーのチェック
上図A~Eの場合は問題なしとし、F~Hの様な場合、辺の交差ありとしています。
連続線の交差
  最初の辺交差の確認では、三点による連続線に対して、中間点が、他の辺の線上からずれている場合は良いのですが、線上にある場合正しく判定出来ません。
そこで、この場合に対する判定を行います。
まず、三点の中間点の点が、辺Klの線上にあるか判断します。
線上にあると判断したら、辺klの角度から辺klが水平で、点が座標が原点0,0になるように座標変換します。
座標変換結果
 上図Aの場合は、三点による連続線の両端が、プラスY側とマイナスY側に分かれているので、辺が交差していると判断します。
B~Fの場合は交差なしとします。
辺の交差があると、正しく計算されません。
 面積の計算は、非常に簡単なのですが、入力ミスの判定にプログラムの大半を使っています。
簡単に判定できるアルゴリズムが見つけられると良いのですが。

プログラム実行
データーの数をセットし、セットボタンをクリックして、入力グリッドの設定をします。
次に、左回りにデーターを入力、全て入力が終わったら、面積計算ボタンをクリックする事で面積が計算されます。

サンプルプログラム

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    DataNoEdit: TLabeledEdit;
    DataNoSet: TButton;
    StringGrid1: TStringGrid;
    CalcBtn: TButton;
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
    procedure DataNoSetClick(Sender: TObject);
    procedure CalcBtnClick(Sender: TObject);
    procedure Area_Calc;            //  面積計算
  private
    { Private 宣言 }
    procedure DataNumberset;        // StringGrid1の設定
    procedure Graph;                // 図形表示
    function Cross_Check(Xs1, Ys1, Xe1, Ye1, Xs2, Ys2, Xe2, Ye2: Double): Boolean;   // 交差チェック
    function Data_Check: Boolean;           // 入力データー確認
    function Cross3Check: Boolean;          // 三点連結線と、直線の交差チェック
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
uses System.Math;

var
  N     : Integer;                  // データーの数
  Xd    : array of double;          // X座標
  Yd    : array of double;          // Y座標
  S     : Double;                   // 面積
  Xmin  : Double;
  Xmax  : Double;
  Ymin  : Double;
  Ymax  : Double;
  Scale : Double;
  GsMax : Double;             // 交差判定時の誤差による語検知防止用

// 三点連結線と、直線の交差チェック
function TForm1.Cross3Check: Boolean;
label
  che;
var
  M : integer;
  i, c, j, k, l : Integer;
  o             : Integer;
  Q1, Q2, Q3    : Double;
  Li, Lj        : Double;
  Yic, Yjc      : Double;
  CF, DF        : Boolean;
  a1, a2        : Double;
  c1, c2        : Double;
  xc, yc        : Double;
  XF, YF        : Boolean;
begin
  Result := True;
  a1 := 0;
  a2 := 0;
  yc := 0;
  xc := 0;
  c1 := 0;
  c2 := 0;
  if N < 5 then exit;
  M := N -1;
  for i := 0 to M do begin
    c := i + 1;                                 // 第一の辺i,c
    if c > M then c := c - M - 1;
    j := c + 1;                                 // 第一の辺c,j
    if j > M then j := j - M - 1;
    // i + 3  は3点連結線の次の点 M + i - 1 は 3点連結線の二つ前
    for o := i + 3 to M + i - 1 do begin
      CF := False;                              // 垂直判定フラグ初期化
      DF := False;
      k := o;
      if k > M then k := k - M - 1;             // 交差対象辺k,l
      l := k + 1;
      if l > M then l := l - M - 1;

      if Xd[i] = Xd[c] then CF := true                      // 最初の辺X差分ゼロフラグ 垂直辺
                       else a1 := (Yd[i] - Yd[c]) / (Xd[i] - Xd[c]);
      if Xd[k] = Xd[l] then DF := true                      // 対象辺X差分ゼロフラグ 垂直辺
                       else a2 := (Yd[k] - Yd[l]) / (Xd[k] - Xd[l]);

      // 傾斜 a の値が計算できたら、定数項 C の値計算
      if not CF then c1 := Yd[c] - a1 * Xd[c];
      if not DF then c2 := Yd[l] - a2 * Xd[l];

      // 垂直平行線の場合
      if CF and DF then goto Che;       // 交差しないので此処まで

      // 二辺とも傾斜 a を計算したら
      if not CF and not DF then begin
        if a1 = a2 then goto Che;       // 傾斜が等しい場合平行なので此処まで
        xc := (c2 - c1) / (a1 - a2);    // 交点Xの計算
        yc := a1 * Xc + c1;             // 交点Yの計算
      end;
      // 最初の辺垂直線の場合の 交点計算
      if CF and not DF then begin
        xc := Xd[i];                  // 最初の辺垂直だったら 最初の辺Xの値
        yc := a2 * xc + c2;           // 交点Yの計算
      end;
      // 対象の辺垂直線の場合の 交点計算
      if DF and not CF then begin
        xc := Xd[k];                  // 対象辺垂直だったら 対象辺Xの値
        yc := a1 * xc + c1;           // 交点Yの計算
      end;
      // 交点が最初の二辺の共通点と一致しなかったら此処まで 1e-10 は演算誤差見込み
      if (abs(xc - Xd[c]) > 1e-10) or  (abs(yc - Yd[c]) > 1e-10) then goto che;

      XF := False;                    // 辺上の点かの判定フラグ初期化
      YF := False;

      // Xc と yc が 対象辺の 範囲内か確認
      // X 方向確認
      if Xd[l] = Xd[k] then XF := True
      else begin
        if Xd[l] > Xd[k] then begin
          if (Xd[c] > Xd[k]) and  (Xd[l] > Xd[c]) then XF := True;
        end
        else
          if (Xd[c] < Xd[k]) and  (Xd[l] < Xd[c]) then XF := True;
      end;
      // y 方向確認 
      if Yd[l] = Yd[k] then YF := True
      else begin
        if Yd[l] > Yd[k] then begin
          if (Yd[c] > Yd[k]) and  (Yd[l] > Yd[c]) then YF := True;
        end
        else
          if (Yd[c] < Yd[k]) and  (Yd[l] < Yd[c]) then YF := True;
      end;
      // 範囲内だったら座標変換 三点2辺の両端の点座標 対象辺の角度で水平方向へ座標変換
      if XF and YF then begin
        Q1 := arctan2(Yd[c] - Yd[i], Xd[c] - Xd[i]);
        Q2 := arctan2(Yd[c] - Yd[j], Xd[c] - Xd[j]);
        Q3 := arctan2(Yd[l] - Yd[k], Xd[l] - Xd[k]);
        Li := sqrt((Yd[c] - Yd[i]) * (Yd[c] - Yd[i]) + (Xd[c] - Xd[i]) * (Xd[c] - Xd[i]));
        Lj := sqrt((Yd[j] - Yd[c]) * (Yd[j] - Yd[c]) + (Xd[j] - Xd[c]) * (Xd[j] - Xd[c]));
        Yic := sin(Q1 - Q3) * Li;
        Yjc := sin(Q2 - Q3) * Lj;
        if abs(Yic) < 1e-10 then Yic := 0;  // 1e-10 は演算誤差見込み
        if abs(Yjc) < 1e-10 then Yjc := 0;  // 1e-10 は演算誤差見込み
        // 三点2辺の両端の点座標 Y方向が プラスとマイナス方向にあったら交差あり
        if ((Yic > 0) and (Yjc < 0)) or ((Yic < 0) and (Yjc > 0)) then Result := False;
      end;
che:
    end;
  end;
//  if not Result then Application.MessageBox('*交差している辺があります*。','注意', 0);
end;

// 座標点を結ぶ直線が交差していないか確認します
// 交差なし True 交差あり False
function TForm1.Cross_Check(Xs1, Ys1, Xe1, Ye1, Xs2, Ys2, Xe2, Ye2: Double): Boolean;  // 交差チェック
var
  a1, a2  : Double;           // 傾斜角
  c1, c2  : Double;
  af1,af2 : Boolean;
  Xa      : Double;           // 交差するXの値
  Ya      : Double;           // 交差するYの値
  f1,  f2 : Boolean;
begin
  Result := True;             // 戻り値初期設定必要
  af1 := False;               // 辺1垂直辺フラグ False 初期設定必要
  af2 := False;               // 辺2垂直辺フラグ False 初期設定必要
  f1  := true;                // 辺1交点なしフラグ初期設定必要
  f2  := true;                // 辺2交点なしフラグ初期設定必要
  a1 := 1000;                 // 辺1傾斜
  a2 := 1000;                 // 辺2傾斜
  Xa := 0;                    // 交点X座標
  Ya := 0;                    // 交点Y座標
  c1 := 0;                    // 辺1定数項
  c2 := 0;                    // 辺2定数項
  // 一次方程式 Y = aX + C の a の値計算 座標 Xs Xeの値が等しい場合 a の値が
  // がないのでフラグをセットします。
  if Xe1 = Xs1 then af1 := true                      // X1差分ゼロフラグ 垂直辺
               else a1 := (Ye1 - Ys1) / (Xe1 - Xs1);
  if Xe2 = Xs2 then af2 := true                      // X2差分ゼロフラグ 垂直辺
               else a2 := (Ye2 - Ys2) / (Xe2 - Xs2);
  // 垂直平行線の場合
  if af1 and af2 then exit;     // 交差しないので此処まで

  // 二辺とも傾斜 a を計算したら
  if not af1 and not af2 then
    if a1 = a2 then exit;       // 傾斜が等しい場合平行なので此処まで

  // 傾斜 a の値が計算できたら、定数項 C の値計算
  if not af1 then c1 := Ys1 - a1 * Xs1;
  if not af2 then c2 := Ys2 - a2 * Xs2;

  // 垂直線の場合の 交点 X の値
  if af1 then Xa := Xs1;        // 辺1垂直だったら X1の座標値 交点Xの値
  if af2 then Xa := Xs2;        // 辺2垂直だったら X2の座標値 交点Xの値

  // 交点が辺の範囲内かチェック
  // 交点があったら f を False にセット
  // 両方とも垂直でも平行でも無い場合
  if not af1 and not af2 then begin
    Xa := (c2 - c1) / (a1 - a2);              // 交点Xの値計算
    // 辺1チェック
    if Ys1 = Ye1 then begin
      if (Ys2 = Ys1) or (Ye2 = Ys1) then exit;
    end;
    if Xs1 < Xe1 then begin
      if (Xa > Xs1) and (Xa < Xe1) then f1 := False;
    end
    else
      if (Xa < Xs1) and (Xa > Xe1) then f1 := False;

    // 辺2チェック
    if Ys2 = Ye2 then begin
      if (Ys1 = Ys2) or (Ye1 = Ys2) then exit;
    end;
    if Xs2 < Xe2 then begin
      if (Xa > Xs2) and (Xa < Xe2) then f2 := False;
    end
    else
      if (Xa < Xs2) and (Xa > Xe2) then f2 := False;
    // 交点があると判定されたら戻り値を False に設定
    if not f1 and not f2 then Result := False;
    exit;
  end;
  // 辺1が垂直の場合
  if af1 then
    if (Xs2 = Xa) or (Xe2 = Xa) then exit;  // 辺2の両端点のXのどちらかがXaと同じだったら此処まで
  // 辺2が垂直の場合
  if af2 then
    if (Xs1 = Xa) or (Xe1 = Xa) then exit;  // 辺1の両端点のXのどちらかがXaと同じだったら此処まで
  // 片方が垂直線の場合 Y の値確認
  f1 := True;
  f2 := True;
  // X の値からY の値計算
  if not af1 then Ya := a1 * Xa + c1;
  if not af2 then Ya := a2 * Xa + c2;
  // 辺1確認
  if Ys1 < Ye1 then begin
    if (Ya > Ys1) and (Ya < Ye1) then f1 := False;
  end
  else
    if (Ya > Ye1) and (Ya < Ys1) then f1 := False;
  // 辺2確認
  if Ys2 < Ye2 then begin
    if (Ya > Ys2) and (Ya < Ye2) then f2 := False;
  end
  else
    if (Ya > Ye2) and (Ya < Ys2) then f2 := False;
  // 交点があると判定されたら戻り値を False に設定
  if not f1 and not f2 then Result := False;
end;

function TForm1.Data_Check: Boolean;        // 入力データー確認
var
  i, j, k : Integer;
begin
  Result := true;                          // 戻り値初期設定
  for i := 0 to N - 2 do                    // N - 1 が最後の点なのでその1つ前までループ
    for k := i + 2 to N - 1 do begin        // i + 2 から最後の点までループ (i + 1の点は二辺共通点なので交差なし)
      if k < N - 1 then j := k + 1          // 最後の点でなかったら次の点は一つ先
                   else j := 0;             // 最後の点だったら、次の点は最初の点
      // 二辺の交差チェック True で交差なし
      if i <> j then                        // i と j が等しいと二辺の共通点なので交差なし
        Result := Cross_Check(Xd[i], Yd[i], Xd[i + 1], Yd[i + 1], Xd[k], Yd[k], Xd[j], Yd[j]);
      if not Result then exit;
    end;
end;

procedure TForm1.Graph;             // 図形表示
var
  j,m     : Integer;
  Xdiff : Double;
  Ydiff : Double;
  Xp,Yp : Integer;
begin
  // 前の図形消去
  Image1.Canvas.Brush.Style := bsSolid;
  Image1.Canvas.Brush.Color := clWhite;
  Image1.Canvas.FillRect(Rect(0, 0, Image1.Width, Image1.Height));
  // 最小値最大値検索
  Xmin := MinValue(Xd);
  Xmax := MaxValue(Xd);
  Ymin := MinValue(Yd);
  Ymax := MaxValue(Yd);
  // 範囲計算
  Xdiff := Xmax - Xmin;
  Ydiff := Ymax - Ymin;
  // 表示スケール設定
  if Xdiff > Ydiff then begin
    GsMax := Xdiff;
    Scale := 250 / Xdiff;
  end
  else begin
    GsMax := Ydiff;
    Scale := 250 / Ydiff;
  end;
  // 辺のと座標点の作図
  for j := 0 to N do begin                          // j が Nになったら最初の座標点
    if j = N then m := 0                            // 一周後の配列の最初の値
             else m := j;                           // 通常の座標
    Xp :=  50 + Round((Xd[m] - Xmin) * Scale);      // Xの位置計算
    Yp := 350 - Round((Yd[m] - Ymin) * Scale) - 50; // Yの位置計算
    Image1.Canvas.Pen.Style := psSolid;
    Image1.Canvas.Pen.Color := clBlue;
    Image1.Canvas.Pen.Width := 1;
    if j = 0 then Image1.Canvas.MoveTo(Xp, Yp)      // 最初の点へ移動
             else Image1.Canvas.LineTo(Xp, Yp);     // 最初の点でなかったら線を引く
    if j < N then begin                             // 最初の点に戻っていなかったら実行
      Image1.Canvas.Pen.Color := clBlack;
      Image1.Canvas.Brush.Color := clRed;
      Image1.Canvas.Ellipse(Xp - 2, Yp - 2, Xp + 3, Yp + 3);  // 円を描画中を塗りつぶし
    end;
  end;
end;

// 面積計算    1/2Σ(Xk - Xk+1)(Yk + Yk+1)
procedure TForm1.Area_Calc;
var
  K : Integer;
  J : Integer;
begin
  S := 0;
  for K := 0 to N -1 do begin
    if K < N - 1 then J := K + 1                  // 次の座標 j = k + 1
                 else J := 0;                     // 最後の座標になったら 次の座標は最初の座標 j = 0
    S := S + (Xd[K] - Xd[J]) * (Yd[K] + Yd[J]);   // Σ(Xk - Xj)(Yk + Yj)
  end;
  S := Abs(S / 2);                                // 1/2 にして絶対値
  Form1.Canvas.TextOut(50, 15, '面積=                                   ');
  Form1.Canvas.TextOut(50, 15, '面積= ' + floatTostr(S));
  Graph;                                          // 図形表示
end;

procedure TForm1.CalcBtnClick(Sender: TObject);   // 入力データー処理 と 面積計算 辺の交差確認
var
  Check : Integer;
  I     : Integer;
  Cmt   : Pchar;
begin
  for I := 0 to N - 1 do begin
    Val(StringGrid1.Cells[1, I + 1], Xd[I], Check);
    if Check <> 0 then begin
      Cmt := PChar('Xデーター' + intTostr(I + 1) + '番目に間違いがあります');
      Application.MessageBox(Cmt,'注意', 0);
      Break;
      exit;
    end;
  end;
  for I := 0 to N - 1 do begin
    Val(StringGrid1.Cells[2, I + 1], Yd[I], Check);
    if Check <> 0 then begin
      Cmt := PChar('Yデーター' + intTostr(I + 1) + '番目に間違いがあります');
      Application.MessageBox(Cmt,'注意', 0);
      Break;
      exit;
    end;
  end;
  Area_Calc;                      // 面積計算
  if not Data_Check then          // 辺の交差確認
    Application.MessageBox('交差している辺があります。','注意', 0);
  if not Cross3Check then
    Application.MessageBox('*交差している辺があります*。','注意', 0);
end;

procedure TForm1.DataNumberset;   // StringGrid1の設定 XY配列の確保
var
  K : Integer;
begin
  Val(DataNoEdit.Text, N, K);     // データーの数 数値変換
  if K <> 0 then begin
    Application.MessageBox('データーの数に間違いがあります','注意', 0);
    exit;
  end;
  if N < 3 then begin
    Application.MessageBox('データーの数は3又は3以上にして下さい。','注意', 0);
    exit;
  end;
  K := 3;                         // 列数 3
  StringGrid1.Width := (StringGrid1.DefaultColWidth + 1) * K  + 3;
  if N < 11 then begin            // データー数11以下の場合
    StringGrid1.ScrollBars := ssNone;
    K := N + 1;                   // 行数 N + 1
    StringGrid1.Height := (StringGrid1.DefaultRowHeight + 1) * K + 3;
  end else
  begin
    K := 11;                      // 行数 10 + 1
    StringGrid1.ScrollBars := ssVertical;
    StringGrid1.Width := StringGrid1.Width + 18;
    StringGrid1.Height := (StringGrid1.DefaultRowHeight + 1) * K + 3;
  end;
  StringGrid1.RowCount := N + 1;  // 行数セット
  for K := 1 to N + 1 do          // データー番号グリッドに表示
    StringGrid1.Cells[0 , K] := InttoStr(K);
  SetLength(Xd, N);               // X座標配列の確保
  SetLength(Yd, N);               // y座標配列の確保
end;

procedure TForm1.DataNoSetClick(Sender: TObject);
begin
  DataNumberset;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  BitMapD : Tbitmap;
begin
  ClientHeight := 410;
  ClientWidth  := 840;
  StringGrid1.Cells[0 , 0] := '';
  StringGrid1.Cells[1 , 0] := 'X';
  StringGrid1.Cells[2 , 0] := 'Y';
// ************* テストデーター ************
  DataNoEdit.Text := '5';
  StringGrid1.Cells[1 , 1] := '0';
  StringGrid1.Cells[2 , 1] := '0';
  StringGrid1.Cells[1 , 2] := '20';
  StringGrid1.Cells[2 , 2] := '0';
  StringGrid1.Cells[1 , 3] := '10';
  StringGrid1.Cells[2 , 3] := '10';
  StringGrid1.Cells[1 , 4] := '20';
  StringGrid1.Cells[2 , 4] := '20';
  StringGrid1.Cells[1 , 5] := '0';
  StringGrid1.Cells[2 , 5] := '20';
// *****************************************
  DataNumberset;
  Image1.Width  := 350;
  Image1.Height := 350;
  BitMapD := Tbitmap.Create;
  BitMapD.Width := Image1.Width;
  BitMapD.Height := Image1.Height;
  Image1.Picture.Graphic := BitMapD;
  BitMapD.Free;
end;

end.

download Polygon_area.zip


各種プログラム計算例に戻る