オウムガイの螺旋模様は黄金螺旋といわれている。このほか、自然界には黄金螺旋が見られるが、なぜその比率になっているかは分かっていない。
今回は、PHPを使って黄金螺旋を描くプログラムを作ってみる。
サンプル・プログラム:実行例
実行時に、いくつかのURLパラメータを渡すことができる。
【例】GoldenSpiral.php?trans=1&n=12
背景を透明にして、12段階の螺旋を描く。
| パラメータ | 内容 | 範 囲 | デフォルト値 |
|---|---|---|---|
| type | 描画モード | 1:黄金長方形のみ描く,2:黄金螺旋のみ描く,3:両方を描く | 3 |
| inspect | 検証モード | 0:通常モード,1:検証モード | 0 |
| margin | 左右マージン(ピクセル) | 0~80 | 1 |
| trans | 背景を透過するかどうか | 0:背景色で塗りつぶす,1:透過する | 0 |
| width | 描画領域の横幅(ピクセル) | 100~1000 | 600 |
| n | 頂点の数 | 0~15 | 10 |
| thick1 | 黄金長方形の線の太さ | 1~10 | 1 |
| thick2 | 黄金螺旋の線の太さ | 1~10 | 3 |
| bgcolor | 背景色 | 000000~FFFFFF | FFFFFF |
| color1 | 黄金長方形の描画色 | 000000~FFFFFF | 000000 |
| color2 | 黄金螺旋の描画色 | 000000~FFFFFF | FF0000 |
サンプル・プログラム
| GoldenSpiral.php | サンプル・プログラム本体 |
| pahooInputData.php | データ入力に関わる関数群。 使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 1.2.0 | 2026/03/01 | PHP8.5対応:double,imagedestroyを使わない, pahooInputData.php導入 |
| 1.1 | 2022/04/24 | PHP8対応,リファラ・チェック改良 |
| 1.0 | 2019/04/10 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 2.0.1 | 2025/08/11 | getParam() bug-fix |
| 2.0.0 | 2025/08/11 | pahooLoadEnv() 追加 |
| 1.9.0 | 2025/07/26 | getParam() 引数に$trim追加 |
| 1.8.1 | 2025/03/15 | validRegexPattern() debug |
| 1.8.0 | 2024/11/12 | validRegexPattern() 追加 |
準備:pahooInputData 関数群
また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。
準備:各種定数など
stationsearch.php
56: // 各種定数(START) ===========================================================
57:
58: // 地図描画サービスの選択
59: // 0:Google
60: // 2:地理院地図・OSM
61: define('MAPSERVICE', 2);
62:
63: // 住所検索サービスの選択
64: // 0:Google
65: // 1:Yahoo!ジオコーダAPI
66: // 11:HeartRails Geo API
67: // 12:OSM Nominatim Search API
68: // 13:国土地理院ジオコーディングAPI
69: define('GEOSERVICE', 1);
70:
71: // 逆ジオコーディングサービスの選択
72: // 0:Google
73: // 1:Yahoo!JAPAN
74: // 11:HeartRails Geo API
75: // 21:簡易ジオコーディングサービス
76: define('REVGEOSERVICE', 1);
77:
78: // マップの表示サイズ(単位:ピクセル)
79: define('MAP_WIDTH', 600);
80: define('MAP_HEIGHT', 400);
81: // マップID
82: define('MAPID', 'map_id');
83: // マップ中心マーカーのURL;表示したくなければNULLにする
84: define('CENTER_MARKER', 'https://maps.google.com/mapfiles/ms/micons/ltblu-pushpin.png');
85:
86: // 初期値
87: define('DEF_LATITUDE', 35.7); // 緯度
88: define('DEF_LONGITUDE', 139.7); // 経度
89: define('DEF_TYPE', 'roadmap'); // マップタイプ
90: define('DEF_ZOOM', 13); // ズーム
91: define('DEF_CATEGORY', 'address'); // カテゴリ
92:
93: // 検索キーの最小文字長
94: define('QUERY_MIN_LEN', 3);
95:
96: // 検索キーの最大文字長
97: define('QUERY_MAX_LEN', 99);
98:
99: // 各種定数(END) ===============================================================
検証モードで使うカラーおよび線分の太さは、INSPECT_ ではじまる定数に記述する。
これらの値は自由に変更できる。
解説:各種パラメータを取得する
GoldenSpiral.php
96: /**
97: * $_GET または $_POST から正規化したデータを取り出す
98: * @param array $items データを格納する配列
99: * @param string $errmsg エラーメッセージを格納する
100: * @return bool TRUE:取得成功/FALSE:失敗
101: */
102: function getParameters(&$items, &$errmsg) {
103: static $tbl_val = array(
104: // 描画モード
105: 'mode'=>array('type'=>TRUE, 'min'=>1, 'max'=>3, 'def'=>3),
106: // 検証モード
107: 'inspect'=>array('type'=>TRUE, 'min'=>0, 'max'=>1, 'def'=>0),
108: // 左右マージン(ピクセル)
109: 'margin'=>array('type'=>TRUE, 'min'=>0, 'max'=>80, 'def'=>1),
110: // 背景を透過するかどうか
111: 'trans'=>array('type'=>TRUE, 'min'=>0, 'max'=>1, 'def'=>0),
112: // 描画領域の横幅(ピクセル)
113: 'width'=>array('type'=>FALSE, 'min'=>100, 'max'=>1000, 'def'=>WIDTH),
114: // 頂点の数
115: 'n'=>array('type'=>TRUE, 'min'=>0, 'max'=>15, 'def'=>10),
116: // 黄金長方形の線の太さ
117: 'thick1'=>array('type'=>TRUE, 'min'=>1, 'max'=>10, 'def'=>1),
118: // 黄金螺旋の線の太さ
119: 'thick2'=>array('type'=>TRUE, 'min'=>1, 'max'=>10, 'def'=>3)
120: );
121: static $tbl_str = array(
122: // 背景色
123: 'bgcolor'=>array('pat'=>'/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i', 'def'=>'FFFFFF'),
124: // 黄金長方形の描画色
125: 'color1'=>array('pat'=>'/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i', 'def'=>'000000'),
126: // 黄金螺旋の描画色
127: 'color2'=>array('pat'=>'/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i', 'def'=>'FF0000')
128: );
129: $errmsg = '';
130: foreach ($tbl_val as $key=>$arr) {
131: $items[$key] = getValidNumber($key, $errmsg, $arr['def'], $arr['type'], $arr['min'], $arr['max']);
132: if ($errmsg != '') return FALSE;
133: }
134: foreach ($tbl_str as $key=>$arr) {
135: $items[$key] = getValidString($key, $errmsg, $arr['def'], 1, 9999, TRUE, array($arr['pat']));
136: if ($errmsg != '') return FALSE;
137: }
138: return TRUE;
139: }
配列 $tbl_val および $tbl_str を変更することで、パラメータの範囲やデフォルト値を変更できる。
解説:頂点パラメータを計算
GoldenSpiral.php
153: /**
154: * 頂点パラメータを計算
155: * @param int $n 頂点の数
156: * @return array 頂点パラメータ
157: * [$i][0] X座標(0.0以上1.0以下)
158: * [$i][1] Y座標(0.0以上1.0以下)
159: * [$i][2] 辺の長さ(0.0以上1.0以下)
160: * [$i][3] 方向(0度以上360度以下)
161: */
162: function calcApexes($n) {
163: $k = RATIO;
164: $p = array();
165: if ($n == 0) return $p;
166:
167: // 始点
168: $p[0][0] = 1.0;
169: $p[0][1] = 0.0;
170: $p[0][2] = 1.0;
171: $p[0][3] = 90;
172:
173: // 頂点座標の計算
174: for ($i = 1; $i < $n; $i++) {
175: $p[$i][2] = $p[$i - 1][2] * ($k - 1);
176: switch ($p[$i - 1][3]) {
177: case 90:
178: $p[$i][0] = $p[$i - 1][0];
179: $p[$i][1] = $p[$i - 1][1] + $p[$i - 1][2] - $p[$i][2];
180: $p[$i][3] = 0;
181: break;
182: case 0:
183: $p[$i][0] = $p[$i - 1][0] + $p[$i - 1][2] - $p[$i][2];
184: $p[$i][1] = $p[$i - 1][1];
185: $p[$i][3] = 270;
186: break;
187: case 270:
188: $p[$i][0] = $p[$i - 1][0];
189: $p[$i][1] = $p[$i - 1][1] - $p[$i - 1][2] + $p[$i][2];
190: $p[$i][3] = 180;
191: break;
192: case 180:
193: $p[$i][0] = $p[$i - 1][0] - $p[$i - 1][2] + $p[$i][2];
194: $p[$i][1] = $p[$i - 1][1];
195: $p[$i][3] = 90;
196: break;
197: }
198: }
199: return $p;
200: }
ユーザー関数 calcApexes は、指定した数だけ頂点の座標を計算して配列に格納する。あわせて、黄金長方形の1辺の長さ(=黄金螺旋の半径)、次の正方形を配置する方向(=螺旋の描画開始角度)を計算し、同じ配列に格納する。
座標計算は、方向に応じて場合分けすることで計算する。1辺の長さは、黄金比に応じて小さくなってゆく。
解説:黄金長方形を描く
GoldenSpiral.php
202: /**
203: * 黄金長方形を描く
204: * @param array $p 頂点パラメータ
205: * @param obj $image イメージストリーム
206: * @param int $width 描画領域の横幅(ピクセル)
207: * @param int $margin 左右マージン(ピクセル)
208: * @param int $color 色ID
209: * @param int $thick 太さ(省略時=1)
210: * @param bool $inspect FALSE:通常モード(デフォルト)/TRUE:検証モード
211: * @return bool TRUE:成功/FALSE:失敗
212: */
213: function drawRect($p, $image, $width, $margin, $color, $thick=1, $inspect=FALSE) {
214: $res = TRUE;
215: $w = (float)($width - $margin * 2) / RATIO;
216:
217: // 一番外側の長方形
218: imagesetthickness($image, INSPECT_THICK2);
219: imagerectangle($image, $margin, (int)($margin / RATIO), $width - $margin, (int)($margin / RATIO + floor($w)), INSPECT_COLOR1);
220:
221: // 黄金長方形
222: $n = count($p) - 1;
223: foreach ($p as $i=>$val) {
224: // 始点
225: $x1 = $x0 = $val[0];
226: $y1 = $y0 = $val[1];
227: $len = $val[2];
228: $dir = $val[3];
229: // 正方形を描く
230: for ($j = 0; $j < 4; $j++) {
231: switch ($dir) {
232: case 90:
233: $x2 = $x1;
234: $y2 = $y1 + $len;
235: $dir = 180;
236: break;
237: case 180:
238: $x2 = $x1 - $len;
239: $y2 = $y1;
240: $dir = 270;
241: break;
242: case 270:
243: $x2 = $x1;
244: $y2 = $y1 - $len;
245: $dir = 0;
246: break;
247: case 0:
248: $x2 = $x1 + $len;
249: $y2 = $y1;
250: $dir = 90;
251: break;
252: }
253: if ($x2 < $x0) $x0 = $x2;
254: if ($y2 < $y0) $y0 = $y2;
255: if (! $inspect) {
256: $tck = $thick;
257: $col = $color;
258: } else {
259: if ($i != $n) {
260: $tck = INSPECT_THICK1;
261: $arr = color2rgb(INSPECT_COLOR1);
262: $col = imagecolorallocate($image, $arr[0], $arr[1], $arr[2]);
263: } else {
264: $tck = INSPECT_THICK2;
265: $arr = color2rgb(INSPECT_COLOR2);
266: $col = imagecolorallocate($image, $arr[0], $arr[1], $arr[2]);
267: }
268: }
269: imagesetthickness($image, $tck);
270: $res = imageline($image, (int)($margin + $x1 * $w), (int)($margin / RATIO + $y1 * $w), (int)($margin + $x2 * $w), (int)($margin / RATIO + $y2 * $w), $col);
271: if ($res == FALSE) break;
272: $x1 = $x2;
273: $y1 = $y2;
274: }
275: // 塗りつぶし(検証モード)
276: if ($inspect && ($n == $i)) {
277: $arr = color2rgb(INSPECT_COLOR4);
278: $col2 = imagecolorallocate($image, $arr[0], $arr[1], $arr[2]);
279: $res = imagefill($image, $margin + ($x0 + $len / 2) * $w, $margin + ($y0 + $len / 2) * $w, $col2);
280: }
281: }
282: return $res;
283: }
まず、一番外側の長方形を組み込み関数 imagerectangle を使って描く。
黄金長方形は、実際には正方形を描いてゆくのだが、頂点が必ずしも正方形の左上端とはかぎらないので、方向の値を見ながら正方形の頂点を計算し、組み込み関数 imageline を使って4辺を描画する。
解説:黄金螺旋を描く
GoldenSpiral.php
285: /**
286: * 黄金螺旋を描く
287: * @param array $p 頂点パラメータ
288: * @param obj $image イメージストリーム
289: * @param int $width 描画領域の横幅(ピクセル)
290: * @param int $margin 左右マージン(ピクセル)
291: * @param int $color 色ID
292: * @param int $thick 太さ(省略時=1)
293: * @param bool $inspect FALSE:通常モード(デフォルト)/TRUE:検証モード
294: * @return bool TRUE:成功/FALSE:失敗
295: */
296: function drawSpiral($p, $image, $width, $margin, $color, $thick=1, $inspect=FALSE) {
297: $res = TRUE;
298: $w = (float)($width - $margin * 2) / RATIO;
299:
300: $n = count($p) - 1;
301: foreach ($p as $i=>$val) {
302: if (! $inspect) {
303: $tck = $thick;
304: $col = $color;
305: } else {
306: if ($i != $n) {
307: $tck = INSPECT_THICK1;
308: $arr = color2rgb(INSPECT_COLOR1);
309: $col = imagecolorallocate($image, $arr[0], $arr[1], $arr[2]);
310: } else {
311: $tck = INSPECT_THICK2;
312: $arr = color2rgb(INSPECT_COLOR3);
313: $col = imagecolorallocate($image, $arr[0], $arr[1], $arr[2]);
314: }
315: }
316: imagesetthickness($image, $tck);
317: $res = imagearc($image, (int)($margin + $val[0] * $w), (int)($margin / RATIO + $val[1] * $w), (int)($val[2] * $w * 2), (int)($val[2] * $w * 2), $val[3], $val[3] + 90, $col);
318: if ($res == FALSE) break;
319: }
320: return $res;
321: }
参考サイト
- 黄金比の描き方をまとめました:ALICEMIX
- PHPで雪の結晶を描く:ぱふぅ家のホームページ
- オウムガイ - 鳥羽水族館:ぱふぅ家のホームページ
- 日本橋で化石探検:ぱふぅ家のホームページ
