[PHP]3次スプライン曲線を使ったスプライン補間

Pocket

点と点を線で結んでグラフにする時、点同士の間を補完する必要があります。
単純に直線でつなぐだけの線形補間でもグラフにはできますが、全ての点を通る滑らかな曲線を描画するのであれば3次スプライン曲線を利用するのが一般的です。

spline2
spline1

<?php
$points = array();

$width  = 300;
$height = 100;

for($x=0; $x<=$width; $x+=50){
	$y = rand(0,100);
	$points[] = array($x, $y);
}
$spline = new CubicSpline($points);

$spline->draw($width, $height);

class CubicSpline
{
	private $xx = array();
	private $yy = array();
	private $q = array();
	private $r = array();
	private $s = array();
	private $n;
	private $points;
	
	
	function __construct($points){
		$this->points = $points;
		
		$this->n = $n = count($points) - 1;	// 区間の数
		$h = array();
		$b = array();
		
		// Step1
		for($i=0;$i < $n;$i++){
			$h[$i] = $points[$i + 1][0] - $points[$i][0];
		}
		
		for($i=1;$i < $n;$i++){
			$b[$i] = 2.0 * ($h[$i] + $h[$i - 1]);
			$d[$i] = 3.0 * (($points[$i + 1][1] - $points[$i][1])
				/ $h[$i] - ($points[$i][1] - $points[$i - 1][1]) / $h[$i - 1]);
		}
		
		// Step2
		$g[1] = $h[1] / $b[1];
		for($i=2;$i<$n - 1;$i++){
			$g[$i] = $h[$i] / ($b[$i] - $h[$i - 1] * $g[$i - 1]);
		}
		
		$u[1] = $d[1] / $b[1];
		
		for($i=2;$i<$n;$i++){
			$u[$i] = ($d[$i] - $h[$i - 1] * $u[$i - 1]) / ($b[$i] - $h[$i - 1] * $g[$i - 1]);
		}
		
		// Step3
		$this->r[0] = $this->r[$n] = 0.0;
		$this->r[$n - 1] = $u[$n - 1];
		for($i=$n - 2; $i >= 1; $i--){
			$this->r[$i] = $u[$i] - $g[$i] * $this->r[$i + 1];
		}
		
		//Step4
		for($i=0;$i<$n;$i++){
			$this->q[$i] = ($points[$i+1][1] - $points[$i][1]) / $h[$i] - $h[$i]
				* ($this->r[$i+1] + 2.0 * $this->r[$i]) / 3.0;
			$this->s[$i] = ($this->r[$i + 1] - $this->r[$i]) / (3.0 * $h[$i]);
		}
		
		return;
	}
	
	function value($x){
		$points = $this->points;
		$n = $this->n;
		$i = -1;
		
		// 区間の決定
		for($i1=1;$i1 < $n && $i < 0;$i1++){
			if($x < $points[$i1][0]){
				$i = $i1 - 1;
			}
		}
		if($i < 0){
			$i = $n - 1;
		}
		
		// 計算
		$x2 = $x - $points[$i][0];
		$y = $points[$i][1] + $x2
			* ($this->q[$i] + $x2 * ($this->r[$i] + $this->s[$i] * $x2));
		return $y;
	}
	
	public function draw($width, $height, $hMargin=10, $vMargin=30 ){
		$end = end($this->points);
		
		$image = imagecreatetruecolor($width + $hMargin * 2,
			$height + $vMargin * 2);
			
		$bg     = imagecolorallocate($image, 255, 255, 255);
		$axis   = imagecolorallocate($image, 180, 180,180);
		$color1 = imagecolorallocate($image, 30, 30, 30);
		$color2 = imagecolorallocate($image, 50, 150, 255);
		imagefill($image, 0, 0, $bg);

		imageline($image, $hMargin, $height + $vMargin,
			$hMargin + $width, $height + $vMargin, $axis);
		imageline($image, $hMargin, $vMargin,
			$hMargin, $height + $vMargin, $axis);
		
		$x1 = $y1 = $x2 = $y2 = 0;
		
		for($x=0;$x<$end[0];$x++){
			$x1 = $x2;
			$y1 = $y2;
		
			$x2 = $hMargin + $x;
			$y2 = ($height + $vMargin) - $this->value($x);
			if($x == 0) continue;
		   imageline($image, $x1, $y1, $x2, $y2, $color1);
		}
		
		foreach($this->points as $point){
			list($x, $y) = $point;
			$x = $hMargin + $x;
			$y = ($height + $vMargin) - $y;
			imagefilledrectangle($image, $x - 2, $y - 2,
				$x + 2, $y + 2, $color2);
		}
		
		header('Content-Type: image/jpeg');
		imagejpeg($image, null, 90);
		imagedestroy($image);
		 
		exit;
	}
}

サンプルではランダムに点をプロットしてそれらの点を通るスプライン曲線を描画しています。
クラスをインスタンス化する際に座標 array(x, y) を配列にしたものを渡し、draw() で画像として描画します。

このプログラムは下記参考 URL のソースコードを元に PHP で書きなおしたものです。

参考:
http://www.sist.ac.jp/~suganuma/cpp/2-bu/7-sho/7-sho.htm#e-7-42


Similar Posts:




コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です