SQL + スクリプトで利用する公開天文データベース
2010年8月19日 光赤天連「データ解析の新展開」用資料 [2011年12月22日更新]
山内@宇宙研
SQL編 /
PHP編
注意
- 今日の資料は,かなり量が多いです.
この資料は明日にでも私の Web ページから
閲覧できるようにします.
- したがって,今すぐ理解できなくてもかまいません.
「こういう情報がここにある」それだけわかれば十分です.
- Google で「Chisato Yamauchi」で検索してください.
そこに,この資料を置く予定です.
イントロ -- 今日のお話
カタログを使った解析は新しい時代へ…
【背景】
- 憶を越える天体数を持つカタログが珍しいものではなくなってきた.
- 天体カタログ単体ではく,それに付随する多くの複雑な情報を
データベースに持たせたサービスが登場してきた.
【研究者・サービスの開発者が直面する問題】
- 研究者がカタログとそれに関係する全データをダウンロードし,
何から何までオレオレ手法で解析という,
従来のやり方は非効率になってきている.
- さすがに憶を越える天体カタログはダウンロードしたくないが,
ブラウザやGUIツールだけでは,決まった事しかできなくて困る.例えば…
-
もっとマニヤックな解析をしたいのだけど,どうしたらいいものか….
スクリプトからWebサーバへのアクセスってよくわからないし,
最近だと VOTable っていうのが返ってくる事があって,
何かヤな感じ…
結局,また全部データもってくるかぁ...ヤレヤレorz
- データベースが複雑化すると,すべての研究者のニーズに応えられる
GUIアプリケーションの開発は難しい.
柔軟性を持たせようとすると,結局,コマンドベースになってしまう.
【上記問題に対して研究者ができる事】
- データベースに関する基本的な知識を持ち,
SQL(データベースを操る言語)でデータを取得できるようになれば,
複雑なデータベースから簡単に自分専用のカタログが作成でき,
効率良く解析する事が可能になる.
実際,宇宙研の赤外グループでは,SQL を使う人が増えてきている.
特に,若い人は類推能力が高く,言語の学習が速い!!
- Webサーバへシームレスにアクセスできるスクリプト言語を用いて,
柔軟にカタログへアクセスできるようになれば,
より複雑な解析にも対応しやすくなる.
具体的には,必要な時に,必要な部分を,必要な付加情報をつけて,
サーバーから「効率良く」データを取り出しながら,
オレオレツールで解析,といった事が可能になる.
今日のテーマ: 効率良く必要なカタログデータを取得するための兵器は…!?
- データベースを操る言語: SQL
- Webサーバへのアクセス,文字列処理,配列処理に強いスクリプト言語: PHP
SQL事はじめ
天文データに使われるデータベースシステムって?
SQL文でできる事
- 様々な条件で,多くの場合,非常に高速な検索ができる.
- 各表の任意のカラムだけ選ぶ事ができ,さらに
複数のテーブルの結合が簡単にできる.
- 四則演算はもちろん,数学関数を含む多くの関数が使える.
- サービスによっては,天文学に特化した関数が利用できる場合がある.
-
SQLが使えれば,オレオレツールを作ってそれを使うよりも
はるかに効率的にカタログに関する全般的処理が可能.
SQLって難しくない?
- データベースの構築(開発)は難しいが,
すでにあるものを利用するだけなら難しくない.
- 類推能力が高い若手研究者なら,検索結果ページに表示される
SQL文を参考にするだけでも,かなりの事ができるようになるはず.
SQL の入力を受けつけている公開サービスを発見したら…
- 「しめしめ」と思う事が大切.
- テーブル,ビュー(View),関数について,ざっと把握する事が大切.
必要なデータがここに多く入っていればいるほど,
効率良く研究ができる.
- テーブルとテーブルとは何らかの関係を持っている事がほとんどで,
その関係を把握する事が大切.
例えば,天体カタログのテーブル,観測情報を保存したテーブルがあり,
2つが結合されるためのID情報がそれぞれのテーブルに保存されている.
そのID情報は「なんとかID」というカラムである事が多い.
そこを押さえておけば,
その2つのテーブルは簡単に結合する事ができる.
こうして,天体カタログにシーイングなどの観測情報を追加した
自分専用カタログが作成できる.
- SDSS の例:
天体テーブル PhotoObjAll:
objid | skyversion | run | rerun | | fieldid | |
587722951693303809 | 1 | 745 | 40 | ... | 587722951693303808 | ... |
観測情報テーブル Field:
fieldid | | psfwidth_u | psfwidth_g | psfwidth_r | |
587722951693303808 | ... | 1.53136 | 1.4194 | 1.362953 | ... |
↓上記2つを結合したテーブルが簡単に作れる.
objid | skyversion | run | rerun | | fieldid | | psfwidth_u | psfwidth_g | psfwidth_r | |
587722951693303809 | 1 | 745 | 40 | ... | 587722951693303808 | ... | 1.53136 | 1.4194 | 1.362953 | ... |
ビュー(View)とは…
- テーブルの一部のカラム・ロウを抜きだしたり,
複数のテーブルを結合した,「仮想的なテーブル」
したがって,
ビューの内容は,テーブルの内容のコピー
(そのままのコピーではなく,加工される事もある).
- ユーザから見た場合,扱いはテーブルと同じ.
SQL で Hello World
- 必ず「SELECT」から始めます.
文字列を除き,大文字小文字は区別されません.
文字列はシングルクォートで囲みます.
SELECT 'Hello World'
結果↓
SQL で計算
演算子と数学関数
関数の種類
- SQL文で使える関数は「ストアドファンクション」と呼ばれる.
- ストアドファンクションには 3 つの種類がある.
- 1つの値を返す関数
数学関数のように,RDBMS に最初からついてくる関数はほとんどがこれ.
- 複数の型(例えば,文字列と浮動小数点型)からなる,1つの行を返す関数.
C言語で言えば,構造体を返すようなもの.
- テーブルを返す関数.
最適化されたアルゴリズムで天体カタログを検索するために,
公開サービスの開発者が仕込む.
これらについて押さえておく事は必須.
テーブルにアクセスしてみる
取得するカラムとアクセスするテーブルを指定
- どのテーブルにアクセスするかは「
FROM
」を使う.
↓AKARI-CAS で FIS カタログの全てのカラムのすべてのロウを取得する例
SELECT *
FROM FisObj
- 特定のカラムを選ぶ場合には,SELECT の後にカンマで区切って列記する.
↓AKARI-CAS で FIS カタログから特定のカラムを取得する例
SELECT objID,objName,ra,dec,
flux_65,flux_90,flux_140,flux_160
FROM FisObj
件数を知る
件数を制限
データベース製品によって異なる.
検索条件を指定
ソート
複数のテーブルを結合する(ナチュラルジョイン)
- 表の結合のための条件を完全に満たすロウのみ取り出して,
2つのテーブルを結合します.
例として,AKARI FISカタログとSIMBADのデータを結合してみましょう.
AKARI-CAS には,SIMBADデータのキャッシュがテーブルとして
保存されています.該当する FIS天体,IRC天体の objID
がそのテーブルに含まれるので,それを使って結合します.
二通りの書き方があります.
[1つめの書き方]:
SELECT f.objid, f.objname, f.ra, f.dec,
f.flux_65, f.flux_90, f.flux_140, f.flux_160, s.*
FROM FisObj f, SimbadFisAll s
WHERE f.objid = s.objid
LIMIT 10
[2つめの書き方]: 「JOIN
〜 ON
」を使う方法:
SELECT f.objid, f.objname, f.ra, f.dec,
f.flux_65, f.flux_90, f.flux_140, f.flux_160, s.*
FROM FisObj f
JOIN SimbadFisAll s
ON f.objid = s.objid
LIMIT 10
↓結果
objid | objname | ra | dec | flux_65 | flux_90 | flux_140 | flux_160 | oid | primident | ra | dec | type | spectype | morphtype | objid | orderdistance | distance |
3008624 | 2056534+372509 | 314.222585840926 | 37.4190989218423 | 2.81637 | 1.49396 | 0.444422 | 0.522871 | 46 | V* V1888 Cyg | 314.22258 | 37.41958 | sr* | null | null | 3008624 | 1 | 0.028866030162561 |
3402204 | 2056153+364325 | 314.063724441542 | 36.7237278098629 | 0.454189 | 0.584841 | 0.0749855 | 0.060682 | 49 | IRAS 20542+3631 | 314.0617 | 36.7214 | IR | null | null | 3402204 | 1 | 0.17025375215859 |
3421072 | 2104400+371643 | 316.166583630238 | 37.2786766426065 | 4.12009 | 2.78826 | 1.03033 | 1.67046 | 83 | V* GR Cyg | 316.1692 | 37.2783 | Mi* | S | null | 3421072 | 1 | 0.12693865228152 |
3406221 | 2052022+363851 | 313.009101608937 | 36.6474694719035 | 0.649305 | 1.52464 | 5.07949 | 4.40738 | 105 | IRAS 20500+3627 | 313.0117 | 36.6453 | IR | null | null | 3406221 | 1 | 0.18052833931278 |
3403727 | 2054389+365716 | 313.662098924202 | 36.9543850472665 | 0.406022 | 0.687735 | null | null | 109 | IRAS 20526+3646 | 313.6637 | 36.9608 | IR | null | null | 3403727 | 2 | 0.39247730460141 |
3018080 | 2057102+360755 | 314.292326538996 | 36.1318868999005 | 0.443806 | 0.575183 | 1.76327 | 0.0832225 | 115 | IRAS 20551+3556 | 314.2942 | 36.1311 | IR | null | null | 3018080 | 1 | 0.1023307947616 |
3407805 | 2102171+364201 | 315.571191023083 | 36.7001622466811 | 1183.95 | 1102.39 | 209.874 | 269.029 | 201 | EQ J2102+3641 | 315.57646 | 36.69364 | * | null | null | 3407805 | 1 | 0.46625774250389 |
3028003 | 2050021+362821 | 312.508951482562 | 36.4726335274307 | 0.393729 | 0.616877 | 2.45721 | 2.63793 | 236 | IRAS 20480+3616 | 312.50804 | 36.47067 | IR | null | null | 3028003 | 1 | 0.125752396588 |
3401377 | 2054342+360742 | 313.6425671115 | 36.1284557101513 | 0.0461723 | 1.10766 | 4.29165 | 1.16297 | 271 | IRAS 20525+3555 | 313.6379 | 36.1242 | IR | null | null | 3401377 | 1 | 0.34111369178287 |
3022907 | 2055267+360710 | 313.86134832208 | 36.119353721714 | 0.644549 | 2.49377 | 3.84965 | 4.44178 | 272 | IRAS 20534+3555 | 313.8529 | 36.1225 | IR | null | null | 3022907 | 1 | 0.45088188424379 |
複数のテーブルを結合する(レフトジョイン)
- 表の結合のための条件を完全に満たさない場合も,
左側のテーブルすべてを出力します.
該当データが存在しない場合,右側のテーブルのカラムは
すべて「null」で埋められます.
この場合,「LEFT JOIN
〜 ON
」を使います.
SELECT f.objid, f.objname, f.ra, f.dec,
f.flux_65, f.flux_90, f.flux_140, f.flux_160, s.*
FROM FisObj f
LEFT JOIN SimbadFisAll s
ON f.objid = s.objid
LIMIT 10
↓結果
objid | objname | ra | dec | flux_65 | flux_90 | flux_140 | flux_160 | oid | primident | ra | dec | type | spectype | morphtype | objid | orderdistance | distance |
3000001 | 2333208+591625 | 353.336859765244 | 59.2737211224275 | 0.200299 | 0.8421 | 0.972306 | 0.32969 | 126939 | IRAS 23310+5900 | 353.3437 | 59.2811 | IR | null | null | 3000001 | 1 | 0.48987245250025 |
3000002 | 2339408+611246 | 354.920083007691 | 61.2127171974234 | 0.767078 | 1.38426 | 8.39954 | 9.55109 | 128300 | 2MASS J23394367+6112374 | 354.932 | 61.21042 | * | B | null | 3000002 | 1 | 0.37089813837837 |
3000003 | 2315499+645218 | 348.957884041822 | 64.8715505030729 | 0.329648 | 0.786866 | 3.30825 | 6.06468 | null | null | null | null | null | null | null | null | null | null |
3000004 | 2334350+612119 | 353.645872488306 | 61.3554164202113 | 0.173503 | 1.21891 | 4.98273 | 3.04551 | null | null | null | null | null | null | null | null | null | null |
3000005 | 2338286+040030 | 354.619335382101 | 4.00819726120836 | 1.03814 | 0.787501 | null | 0.939099 | null | null | null | null | null | null | null | null | null | null |
3000006 | 1556214-470221 | 239.089094535388 | -47.039097579021 | null | 0.776093 | 3.35555 | 0.248287 | null | null | null | null | null | null | null | null | null | null |
3000007 | 2024245+434945 | 306.102274845927 | 43.829113790373 | 1.17064 | 2.17332 | 2.72243 | null | null | null | null | null | null | null | null | null | null | null |
3000008 | 2314215+624130 | 348.589648092642 | 62.691536648297 | null | 1.57482 | 5.44634 | 4.47007 | null | null | null | null | null | null | null | null | null | null |
3000009 | 1753189-241150 | 268.328827159812 | -24.1971547030389 | null | 3.6197 | 14.1353 | 13.9065 | null | null | null | null | null | null | null | null | null | null |
3000010 | 2339198+615719 | 354.832503848858 | 61.9552944647441 | 6.12826 | 14.6462 | 24.7868 | 15.2489 | 5252456 | JCMTSE J233920.6+615714 | 354.8358 | 61.9539 | smm | null | null | 3000010 | 1 | 0.12508629271314 |
いよいよ Radial Search (コーンサーチ)
- 天文でよく使う「Radial Search」は RDBMS にとっては
やや特殊な検索であり,
定石とされるテクニックを用いて検索手法を最適化しないと
パフォーマンスが得られません.
したがって,
「WHERE
」の条件にテキトーに条件を入れても
速い検索はできません.
- Radial Search を行なうには,
公開されているそれぞれのデータベース専用に用意された
ストアドファンクションを使います.
そのストアドファンクションが,最適な方法で検索を行なってくれます.
- SDSS の場合
テーブルを返す関数「fGetNearbyObjEq()
」を使います.
↓R.A.=182.0471, Dec.=2.8788, 半径=0.5arcmin 内の天体を取得.
SELECT *
FROM fGetNearbyObjEq(182.0471, 2.8788, 0.5)
↓結果
objID | run | camcol | field | rerun | type | cx | cy | cz | htmID | distance |
587726015606685723 | 1458 | 4 | 382 | 40 | 3 | -0.99810157 | -0.03566194 | 0.05021442 | 14846242813230 | 0.05731578 |
587726015606685730 | 1458 | 4 | 382 | 40 | 3 | -0.99810009 | -0.03570173 | 0.05021554 | 14846242735641 | 0.09265617 |
587726015606685736 | 1458 | 4 | 382 | 40 | 3 | -0.99809836 | -0.03579129 | 0.05018606 | 14846310057009 | 0.41682857 |
587726015606685734 | 1458 | 4 | 382 | 40 | 3 | -0.99809764 | -0.03580067 | 0.05019376 | 14846310070453 | 0.44080137 |
ただし,この関数が返してくれるカラムは非常に少ないので,
天体テーブルと objID を使ってジョインします.
次の例では,objid,座標,等級,距離 を取得しています.
SELECT p.objid, p.ra, p.dec, p.u, p.g, p.r, p.i, p.z, f.distance
FROM photoobj p, fGetNearbyObjEq(182.0471, 2.8788, 0.5) f
WHERE p.objid = f.objid
↓結果
objid | ra | dec | u | g | r | i | z | distance |
587726015606685723 | 182.04629444 | 2.87828477 | 15.363654 | 13.597462 | 12.750392 | 12.363647 | 12.105381 | 0.05731578 |
587726015606685730 | 182.04857861 | 2.87834891 | 18.717392 | 22.014599 | 24.43655 | 23.825893 | 18.96513 | 0.09265617 |
587726015606685734 | 182.05425605 | 2.87709976 | 21.464825 | 20.805237 | 20.493608 | 20.357042 | 21.63262 | 0.44080137 |
587726015606685736 | 182.05371694 | 2.8766576 | 21.552475 | 20.821678 | 20.937908 | 21.535532 | 22.231407 | 0.41682857 |
- AKARI-CAS の場合
SDSS の場合と同様に,
テーブルを返す関数「fGetNearbyObjCel()」を使います.
↓FISカタログから,
J2000 で R.A.=182.0471, Dec.=2.8788, 半径=20.0arcmin 内の天体を取得.
SELECT *
FROM fGetNearbyObjCel('fis', 'j2000', 182.0471, 2.8788, 40.0)
↓結果
objid | objname | cx | cy | cz | distance |
3210497 | 1207210+021702 | -0.998691820062417 | -0.0320417640633099 | 0.0398493901600507 | 37.8430471800916 |
3209287 | 1207367+024131 | -0.998345612733355 | -0.0331721880768413 | 0.0469642787047324 | 14.1536549454711 |
3237747 | 1208113+025244 | -0.998100570450325 | -0.0356761964686873 | 0.050224100512247 | 0.00255002622334213 |
3076561 | 1208298+030928 | -0.99779544384722 | -0.0370125160756103 | 0.0550847155886523 | 17.3638417741195 |
このように,AKARI-CASでは,第1引数でカタログ,
第2引数で座標系('j2000', 'b1950', 'gal', 'ecl' のいずれか)を指定できます.
はやり,関数は最低限のカラムしか返さないので,天体カタログとジョインします.
SELECT o.objid, o.objname, o.ra, o.dec,
o.flux_65, o.flux_90, o.flux_140, o.flux_160,
f.distance
FROM fisobj o,
fGetNearbyObjCel('fis', 'j2000', 182.0471, 2.8788, 40.0) f
WHERE o.objid = f.objid
↓結果
objid | objname | ra | dec | flux_65 | flux_90 | flux_140 | flux_160 | distance |
3210497 | 1207210+021702 | 181.837632268152 | 2.28380658205473 | 1.31537 | 0.580465 | 0.538393 | 0.810698 | 37.8430471800916 |
3209287 | 1207367+024131 | 181.90307580294 | 2.69184511852589 | 0.745301 | 1.93466 | 5.21432 | 5.98616 | 14.1536549454711 |
3237747 | 1208113+025244 | 182.047113962863 | 2.87884014862651 | 4.40203 | 6.42683 | 7.45083 | 5.03853 | 0.00255002622334213 |
3076561 | 1208298+030928 | 182.124372393784 | 3.15772001924006 | 0.431957 | 0.656086 | 1.06449 | 1.8309 | 17.3638417741195 |
サブクエリ (絞り込み検索)
- 前ページの最後の Radial Search の結果から,
さらに flux_90 の値で絞り込んでみましょう.
こういう場合は,サブクエリを使うのも1つの方法です.
SELECT p.*
FROM
(
SELECT o.objid, o.objname, o.ra, o.dec,
o.flux_65, o.flux_90, o.flux_140, o.flux_160,
f.distance
FROM fisobj o,
fGetNearbyObjCel('fis', 'j2000', 182.0471, 2.8788, 40.0) f
WHERE o.objid = f.objid
) p
WHERE 1.0 < p.flux_90
「FROM ( SELECT … ) p
」の部分では,括弧内を実行した結果が
1つのテーブル「p
」として扱われます.
上記の場合はテーブル「p
」に対して WHERE 句で条件を追加し,
Radial Search の後にさらに flux_90 で絞り込んでいます.
結果は次のとおりです.
objid | objname | ra | dec | flux_65 | flux_90 | flux_140 | flux_160 | distance |
3209287 | 1207367+024131 | 181.90307580294 | 2.69184511852589 | 0.745301 | 1.93466 | 5.21432 | 5.98616 | 14.1536549454711 |
3237747 | 1208113+025244 | 182.047113962863 | 2.87884014862651 | 4.40203 | 6.42683 | 7.45083 | 5.03853 | 0.00255002622334213 |
登録されているカタログとカタログとのマッチアップ
- データベースに複数の天体カタログが存在する場合,
それらは簡単に座標マッチアップが行なえる可能性があります.
- ケース1: あらかじめマッチアップした結果をテーブルに
入れてしまっている場合.
SDSS の場合は,データベースに
USNO等のカタログが登録されていますが,
それぞれのカタログテーブルに SDSSカタログの objID が保存された
カラムが存在します.この場合は,普通に JOIN すれば
自分専用のカタログが作れます.
- ケース2: ケース1ではない場合
この場合は,動的にマッチアップする必要があります.
AKARI-CAS の場合では,そのためのストアドファンクションを
用意しており,
fGetNearestObjIDEq()
を使えば簡単にマッチアップが
可能です.
次に例を示します.FIS のカタログに,マッチアップ結果となる IRC カタログを
レフトジョインします.
なお,ヒットしない天体を除去したい場合は,ナチュラルジョインを使えば
OK.
SELECT p.objid, p.ra, p.dec,
q1.objid, q1.ra, q1.dec,
(CASE (q1.objid IS NULL) WHEN true THEN null ELSE
fDistanceArcminEq(p.ra,p.dec,q1.ra,q1.dec) END) as distance
FROM
(
SELECT o.*
FROM FisObj o
) p
LEFT JOIN
IrcObj q1
ON -- ↓この関数の最後の引数で検索する半径(arcmin)を指定
fGetNearestObjIDEq('irc', p.ra, p.dec, 1.0) = q1.objID
LIMIT 20
↓結果
objid | ra | dec | objid | ra | dec | distance |
3003719 | 275.526360148979 | -12.1287506682807 | null | null | null | null |
3003959 | 315.01998356022 | 43.4584300018619 | null | null | null | null |
3004182 | 255.639791526551 | -37.0749460127268 | null | null | null | null |
3004387 | 354.415328637996 | 4.60317562391009 | null | null | null | null |
3004643 | 342.100083031894 | 60.525376101338 | null | null | null | null |
3008403 | 286.744817121559 | -34.3068056020533 | null | null | null | null |
3015895 | 338.465004825689 | 65.6947190387677 | null | null | null | null |
3018334 | 294.151039826671 | 20.9372639034434 | null | null | null | null |
3019183 | 309.11103114615 | 44.5429205855969 | null | null | null | null |
3021298 | 298.807560078585 | 27.9678310860942 | null | null | null | null |
3021529 | 288.16177785658 | -3.05675290419978 | null | null | null | null |
3022311 | 284.114160273956 | -4.26804216087505 | null | null | null | null |
3022858 | 313.894368337895 | 50.8988102632035 | null | null | null | null |
3023416 | 290.377041967155 | 8.61037006714733 | null | null | null | null |
3023718 | 265.765181735996 | -28.4550164047165 | 200497694 | 265.763993500315 | -28.4393145000376 | 0.944197455279379 |
3023800 | 311.363027589736 | 42.2199852047827 | null | null | null | null |
3024268 | 311.126984645114 | 38.7322842276614 | null | null | null | null |
3024954 | 284.171843790509 | -5.26706307182863 | null | null | null | null |
3025932 | 310.650628720967 | -28.0867045463197 | null | null | null | null |
3026138 | 291.933154349379 | -14.754686888449 | null | null | null | null |
- 次のように,「
LEFT JOIN 〜 ON
」の後にさらに
「LEFT JOIN 〜 ON
」を続けて書けば,
3つ以上のカタログをマッチアップして結合する事も可能です.
:
LEFT JOIN
IrcObj q1
ON
fGetNearestObjIDEq('irc', p.ra, p.dec, 1.0) = q1.objID
LEFT JOIN
Iras q2
ON
fGetNearestObjIDEq('iras', p.ra, p.dec, 1.0) = q2.objID
LEFT JOIN
IrasFsc q3
ON
fGetNearestObjIDEq('irasfsc', p.ra, p.dec, 1.0) = q3.objID
- 「
CASE 〜 END
」というのを使っていますが,
これは,場合分けを行なう時の構文です.
関数fDistanceArcMinEq()
は J2000 座標にて,
2点間の角度を求めるものですが,
座標値が null の時には計算できないので,CASE文で判定して
その場合は単に null を返すようにしています.
詳細は,
http://www.postgresql.jp/document/8.1/html/functions-conditional.html
をご覧ください.
難しい事ではありません.
- AKARI-CAS の場合,サーチ半径 1 arcmin 以内で,
FISカタログ(427071天体)から IRCカタログ(844649天体)
にマッチアップをかけると,30秒から1分ほどかかります.
ブラウザから利用している場合でかつ
サーチ半径が非常に大きい場合,
途中でタイムアウト(90秒)のエラーで止まります.
座標などで分割するか,
コマンドツール(この場合はタイムアウトが長いです)を使ってください.
- AKARI-CAS には,RC3,IRAS PSC,IRAS FSC カタログが登録されています.
これらは上記の方法で簡単にマッチアップできます.
AKARI-CAS で便利なストアドファンクション
- 座標の表記変換のための関数
deg → xx:xx:xx.x の変換
SELECT objid, ra, dec,
fDeg2LonStr(ra) as ra_hms, fDeg2LatStr(dec) as dec_dms
FROM fisobj
LIMIT 5
↓結果
objid | ra | dec | ra_hms | dec_dms |
3003719 | 275.526360148979 | -12.1287506682807 | 18:22:06.33 | -12:07:43.5 |
3003959 | 315.01998356022 | 43.4584300018619 | 21:00:04.80 | +43:27:30.3 |
3004182 | 255.639791526551 | -37.0749460127268 | 17:02:33.55 | -37:04:29.8 |
3004387 | 354.415328637996 | 4.60317562391009 | 23:37:39.68 | +04:36:11.4 |
3004643 | 342.100083031894 | 60.525376101338 | 22:48:24.02 | +60:31:31.4 |
xx:xx:xx.x → deg の変換
SELECT flonstr2deg('12:08:11.30'), flatstr2deg('+02:52:43.7')
↓結果
flonstr2deg | flatstr2deg |
182.047083333333 | 2.87880555555556 |
- ランダムに n 個取得
SELECT objID,objName,ra,dec,
flux_65,flux_90,flux_140,flux_160
FROM FisObjALL
WHERE
19.0 < flux_90 AND flux_90 <= 20.0
ORDER BY random()
LIMIT 30
- Rectangular Search のための関数
SELECT *
FROM fGetObjFromRectCel('Fis', 'j2000', 180.5, 200.7, 1.5, 3.6)
LIMIT 10
objid | objname | cx | cy | cz |
3067437 | 1240424+030145 | -0.982891922293839 | -0.176439987175946 | 0.0528431643154872 |
3072117 | 1309338+014024 | -0.953880291495469 | -0.29876359212076 | 0.0292011218897408 |
3072244 | 1224402+023017 | -0.993262601932336 | -0.107329313547382 | 0.0437015109127183 |
3072385 | 1234087+023916 | -0.987861300473603 | -0.148274869306229 | 0.0463099790414897 |
3073145 | 1253285+021301 | -0.972174567677781 | -0.231041587495541 | 0.0386832626346237 |
3074388 | 1233553+023713 | -0.98803228983794 | -0.147316928082791 | 0.0457156093456847 |
3076216 | 1243092+033452 | -0.98040647884525 | -0.186819339384805 | 0.0624633546175991 |
3076561 | 1208298+030928 | -0.99779544384722 | -0.0370125160756103 | 0.0550847155886523 |
3076654 | 1235364+030214 | -0.986567582717931 | -0.15452114629284 | 0.0529869802729445 |
3076793 | 1221576+022041 | -0.994579894030844 | -0.0955887934126941 | 0.0409098638900218 |
- 周辺に存在する天体の個数
SELECT objid, ra, dec,
(fGetNearestObjEq('fis',ra,dec,20)).objCount
FROM fisobj
LIMIT 5
↓結果
objid | ra | dec | objcount |
3003719 | 275.526360148979 | -12.1287506682807 | 82 |
3003959 | 315.01998356022 | 43.4584300018619 | 123 |
3004182 | 255.639791526551 | -37.0749460127268 | 52 |
3004387 | 354.415328637996 | 4.60317562391009 | 6 |
3004643 | 342.100083031894 | 60.525376101338 | 45 |
史上最強の手続き言語,PHP のススメ
PHPって?
- PHPは元々,Webアプリケーションを作るための言語として
開発された.
その手軽さと高速動作が大ヒットし,近年急速に広まった.その結果,
Perl CGIを駆逐してしまった.
- ここにきて,サーバだけでなく,ローカルで利用しても
激しく便利である事がバレてしまった.
PHPの何がいいのか
- C言語やPerlの文法を踏襲しているので,
多くの人にとっては,1から勉強しなくて良い.
- 言語特有のクセが非常に少ない.
独自の記号の定義が少なく,可読性が高く,覚えやすい.
「洗練」という言葉がピタリとくる.
これまでの言語での悪いクセの例 (私の主観も入ってます)
- Perlの場合
「~=
」だっけ「=~
」だっけ?
「#$
」だっけ「$#
」だっけ?
配列の個数だっけ? 添字の最大値だっけ?
- C++標準ライブラリの場合
「<<
」だっけ「>>
」だっけ?
っていうか読みにくいよ!!
(ストリームの「<<
」は google 規約では使用禁止だし…)
- IDLの場合
「*
」の濫用による可読性の低下.
「strtrim(hoge,2)
」の「2」って何ですか…?
こういう過去の言語の「ゴチャゴチャ」を徹底的に排除したのが
PHP というわけ.
- 標準関数の充実度が凄い.
Webサーバへのアクセス,文字列処理,
配列処理が簡単.
本日紹介するPHPのコードには,外部ライブラリは一切使いません.
PHPのインストール
Version 5 系列をご利用ください.
- MacOSXの場合
最初から入っています.
- Linuxの場合
# yum install php
# apt-get install php
- Cygwin・Solaris等,その他のUNIXの場合
がんばってください.
ソースからのインストールは難しくありませんが,
CPU が遅いとキツいです
(Sparc だとテストを含めると丸一日かかるという).
PHPの基本
変数
Perlに似ています.宣言は不要で,いきなり代入できます.
- 変数作成と代入
$str1 = "abc"; /* "" の場合は,その中で変数を参照でき,*/
$str2 = "$str1\n"; /* \n 等の特殊文字が変換されます */
$str3 = 'xyz'; /* '' の場合は,そのまんまの代入です */
$suuchi = 1.4142;
$flag = true;
- 配列
$arr = array(); /* 新規配列の作成・初期化 */
$arr = array( "a", "b" ); /* 初期化時に値を入れる事もできる */
$i = 0;
$arr[$i++] = "a";
$arr[$i++] = "b";
$n = count($arr); /* 配列の個数を調べる時 */
- 連想配列
$prms = array( 'db' => 'DR1', /* よくこうやって初期化します */
'format' => 'text' );
$prms['cmd'] = "SELECT * FROM photoobjall";
- 変数情報の確認(デバッグに便利!)
print_r($arr);
このように表示されます↓
Array
(
[0] => a
[1] => b
)
マニヤな人向けに var_dump()
もあります.
文字列処理
- 連結(Perlと同じ)
$moji = "abc" . "def"; /* "abcdef" が $moji に代入される */
- printf
$pai = 3.1415927;
$s_pai = sprintf("%.4f",$pai);
echo $s_pai . "\n"; /* echo で出力してもいいし */
printf("%.4f\n",$pai); /* いきなり printf() でも OK */
- CSV形式の文字列の分割.
$csv_str = "abc,def,xyz,";
$arr = explode(",", $csv_str);
print_r($arr);
このように表示されます↓
Array
(
[0] => abc
[1] => def
[2] => xyz
[3] =>
)
- 文字列の左右の空白や改行文字を削除するのに,
trim()
,rtrim()
を良く使います.
- 正規表現は,
preg_match()
,
preg_split()
,
preg_replace()
等が用意されています.Googleで調べてみましょう.
if文
ループ
C言語やPerlと同じ構文です.
コマンド引数の受け取り
関数の作成
- Cとは異なり,何でも return で返せます.
function my_func($arg_in, &$arg_out) {
$arr = array();
$arg_in = ...; /* これは戻らない */
$arg_out = ...; /* これは呼び出し元に戻る */
return $arr;
}
「&
」がついた引数は,
関数内で書き換えた場合に値が呼び出し元に戻ります.
- C++ と同様,引数の省略を許可するようにも作れます.
function my_func($arg_in, &$arg_out, $options = false) {
:
:
}
この場合,第3引数は指定してもしなくてもこの関数が利用できます
(省略した場合,false が代入されたものとみなされます).
互換性を保ったまま関数の機能を強化したい時に便利です.
ソースファイルの分割
実践編
Webサーバ上のHTMLを取得
- 普通に
fopen()
を使うだけです.
<?php
$fp = fopen("http://darts.isas.jaxa.jp/", "r");
if ( $fp === false ) { /* 失敗した時は,エラーを報告して終了 */
fputs(STDERR, "ERROR: fopen() failed\n");
exit(1);
}
while ( ($s=fgets($fp)) !== false ) echo $s;
fclose($fp);
?>
これを実行すると,HTMLが表示されます.
- ワンポイント
PHPの関数は,様々の型の変数を返す事ができます.
上記の fopen()
は,
成功した場合はファイルの情報を返しますが,
失敗した場合は bool型で「false」を返します.
このような返り値を調べるには,
型情報を含めた比較演算子
「===
」
「!==
」
を使います.
SDSS SkyServerから画像を取得
- Webサーバ上のCGIをオープン
SDSS の SkyServer から画像を取得してみます.
コードの中で,次のような URL:
http://casjobs.sdss.org/ImgCutoutDR7/getjpeg.aspx?ra=182.04710000&dec=2.87880000&scale=1.00000000&width=200&height=200
を作成し,
fopen() で普通にオープンして読み出します.
<?php
/* コマンド引数から座標とスケールを取得 */
$ra = $argv[1];
$dec = $argv[2];
$scale = $argv[3];
/* SkyServer へアクセスして画像を取得 */
$url = "http://casjobs.sdss.org/ImgCutoutDR7/getjpeg.aspx?";
$prms = array( 'ra' => sprintf("%.8f",$ra),
'dec' => sprintf("%.8f",$dec),
'scale' => sprintf("%.8f",$scale),
'width' => "200",
'height' => "200"
);
$fp = fopen($url . http_build_query($prms), "r");
if ( $fp === false ) { /* 失敗した時は,エラーを報告して終了 */
fputs(STDERR, "ERROR: fopen() failed\n");
exit(1);
}
while ( !feof($fp) && ($s=fread($fp, 4096)) !== false ) echo $s;
fclose($fp);
?>
URLの引数は連想配列に用意し,
http_build_query()
を使って「prm1=var1&prm2=var2…
」の形に変換します.
urlencode()
を使う方法もありますが,
この形が最も綺麗だと思います.
- 実行↓
mypc$ php getimage.php 182.0471 2.8788 1.0 > img.jpg
mypc$ display img.jpg &
SIMBADから天体の基本データを取得
- sim-scriptを使ってみます.
http://simbad.u-strasbg.fr/simbad/sim-help?Page=sim-url
,
http://simbad.u-strasbg.fr/simbad/sim-help?Page=sim-fscript
に説明がありますが,けっこう謎な仕様です….
<?php
/* 引数は天体名や座標値 */
$objname = $argv[1];
/* sim-script を含む URL を作る */
$fmt = "%IDLIST(S;1);%COO(d;A;FK5);%COO(d;D;FK5);" .
"%OTYPE(3);%OTYPELIST;%SP(S);%MT(M);%IDLIST[%*(S),]";
$script = "output console=off script=off\n" .
"format obj f1 \"$fmt\"\n" .
$objname;
$url = "http://simbad.u-strasbg.fr/simbad/sim-script?script=" .
urlencode($script);
/* SIMBAD へアクセス */
$fp = fopen($url, "r");
if ( $fp === false ) { /* 失敗した時は,エラーを報告して終了 */
fputs(STDERR, "ERROR: fopen() failed\n");
exit(1);
}
while ( ($s=fgets($fp)) !== false ) echo $s;
fclose($fp);
?>
http_build_query()
を使わない場合は,
URLの引数の値に対しては必ず
関数urlencode()
を使うべきです.これにより,値については
空白→「%20
」,「%
」→「%25
」
のようにエンコードされ,正しいアクセスができます.
- 実行↓
M31銀河について,データを取得してみます.
mypc$ php getsimbad.php M31
M 31;010.68463;+41.26928;LIN;G,GiC,GiG,AGN,LIN,QSO,Rad,IR,X;~;Sb;M 31,2C 56,DA 21,GIN 801,IRAS F00400+4059,IRAS 00400+4059,IRC +40013,K79 1C,LEDA 2557,2MASX J00424433+4116074,MCG+07-02-016,NAME AND NEBULA,NAME ANDROMEDA NEBULA,NAME ANDROMEDA,NAME ANDROMEDA Galaxy,NGC 224,RAFGL 104,UGC 454,XSS J00425+4102,Z 535-17,Z 0040.0+4100,[DGW65] 4,[M98c] 004000.1+405943,[VV2000c] J004244.3+411610,[VV2003c] J004244.3+411610,[VV2006] J004244.3+411610,[VV98c] J004245.1+411622,
AKARI-CAS に SQL でカタログの値を取得
- AKARI-CAS の SQL サーチの API については,
http://darts.jaxa.jp/ir/akari/cas/help/api.html#SQLSEARCH
に説明があります.
今度は,CSV 形式のデータを取得し,連想配列に変換してみます.
<?php
$db = "DR1";
$format = "text";
$cmd = $argv[1]; /* ←SQL文をコマンド引数から受けとる */
/* URL を作る */
$url = "http://darts.jaxa.jp/ir/akari/cas/tools/search/x_sql.php?";
$prms = array( 'db' => $db,
'format' => $format,
'cmd' => $cmd
);
/* AKARI-CAS へアクセス */
$fp = fopen($url . http_build_query($prms), "r");
if ( $fp === false ) { /* 失敗した時は,エラーを報告して終了 */
fputs(STDERR, "ERROR: fopen() failed\n");
exit(1);
}
/* カタログデータの読み取り */
/* カラム名を保存 */
if ( ($s=fgets($fp)) !== false ) {
$col_names = explode(",", rtrim($s));
}
/* 値を連想配列に格納して表示 */
while ( ($s=fgets($fp)) !== false ) {
$col_vals = array();
$v = explode(",", rtrim($s));
for ( $i=0 ; $i < count($v) ; $i++ ) $col_vals[$col_names[$i]] = $v[$i];
print_r($col_vals);
}
fclose($fp);
?>
- 実行↓
R.A.=182.0471, Dec.=2.8788, 半径=1.0arcmin 内の天体を取得.
mypc$ php getakari.php "SELECT o.* FROM fisobj o, fGetNearbyObjEq('fis', 182.0471, 2.8788, 1.0) n WHERE o.objid = n.objid"
Array
(
[objid] => 3237747
[objname] => 1208113+025244
[ra] => 182.047113962863
[dec] => 2.87884014862651
[flux_65] => 4.40203
[flux_90] => 6.42683
[flux_140] => 7.45083
[flux_160] => 5.03853
[fqual_65] => 3
[fqual_90] => 3
[fqual_140] => 3
[fqual_160] => 1
[flags_65] => 0
[flags_90] => 0
[flags_140] => 0
[flags_160] => 0
[nscanc_65] => 5
[nscanc_90] => 5
[nscanc_140] => 6
[nscanc_160] => 4
[nscanp_65] => 5
[nscanp_90] => 5
[nscanp_140] => 7
[nscanp_160] => 6
[mconf_65] => 1
[mconf_90] => 1
[mconf_140] => 1
[mconf_160] => 1
)
NED から VOTable を取得して必要なデータを表示
スクリプトから AKARI-CAS の Cross-ID を利用
- Webサーバへのアクセス方法の
「GET」メソッドと「POST」メソッドとの違いを知る.
Web上のサービスを使った時,結果ページに
http://darts.jaxa.jp/ir/akari/cas/tools/search/x_radial.php?direct=true&coordinate=j2000&lon=266.0&lat=-28.0&radius=10&radius_unit=arcmins&fis=true&db=dr1&columns=digest&rows=limit&limit=10&format=html&distance=true
のように,「?」に続く引数がついている場合は「GET」.
http://darts.jaxa.jp/ir/akari/cas/tools/search/x_upload.php
のように,引数がつかない場合は「POST」です.
つまり,さきほどまでのすべてのサンプルコードでは,実は「GET」の場合でした.
- で,何が違うわけ?
「GET」の場合は,サーバのURLとデータとを一発で送信できて簡単ですが,
長さ制限(8kB程度)があり,大きなデータを引数に与えると
動作は保証されません.
「POST」の場合は,サーバのURLとデータとを分割して送信するので
ちょっと繁雑ですが,データの長さ制限がありません.
したがって,多くのデータを受けつけなければならないサービスでは,
必然的に「POST」を使う事になります.
- 「POST」は,
ユーザの座標リストとマッチアップしてくれる検索サービスで
良く用いられます.
- 次に,AKARI-CAS の「CrossID」サービスを
スクリプトから使う例を示します.
SDSS の SkyServer でも同じ手が使えます.
<?php
/* サーバのURL */
$url = 'http://darts.jaxa.jp/ir/akari/cas/tools/search/x_upload.php';
/* サーバへ送信するデータを作成 */
$prms = array( 'MAX_FILE_SIZE' => '81920', /* ←AKARI-CAS の場合は固定値 */
'db' => 'DR1',
'format' => 'text',
'delimiter' => ',',
'scope' => 'nearobj',
'radius' => '0.5',
'req_type' => 'fis_summary',
'paste' => "A1 195.1620 2.5028\n" .
"A2 162.9899 3.7919\n" .
"A3 12:55:15.26 +02:53:48.8\n" .
"A4 02 52 20.38 -87 20 22.6\n"
);
$headers = array( 'Content-type: application/x-www-form-urlencoded' );
$options = array( 'http' => array( 'method' => 'POST',
'header' => implode("\r\n", $headers),
'content' => http_build_query($prms)
)
);
/* 結果の取得 */
$result = file_get_contents($url, false, stream_context_create($options));
echo $result;
?>
- 実行↓
mypc$ php akaricrossid.php
id_x,name_x,ra_x,dec_x,seqno,objcount,distance,objid
1,A1,195.162,2.5028,1,1,0.133612661328217,3151469
2,A2,162.9899,3.7919,2,1,0.132057644009147,3075896
3,A3,193.813583333333,2.89688888888889,3,1,0.133935877844111,3181130
4,A4,43.0849166666667,-87.3396111111111,4,1,0.13182048667748,3123441
- 「POST」の場合も,API が公開されていれば
スクリプトから使うのは自由です.
AKARI-CAS の API:
http://darts.jaxa.jp/ir/akari/cas/help/api.html#CROSSID
SDSS の API:
http://skyserver.sdss3.org/dr8/en/help/docs/api.asp#cross
SDSSについては,この後に例を示します.
- このように,「POST」がスクリプトから使えるようになれば,
場合によっては
短時間で大量のデータをサーバに喰わせる事が可能です.
スクリプトから SDSS の Cross-ID を利用
SDSSの場合,application/x-www-form-urlencoded
の形式では受け付けていないようなので,
multipart/form-data
の形式でデータを投げるようにします.例を示します:
<?php
/* multipart/form-data 形式の送信データを作る */
/* $boundary : 境界文字列 */
/* $prms : フォームのパラメータ(連想配列) */
/* $upload_name : ファイルのアップロードに用いるフォームのパラメータ名 */
/* $upload_str : アップロードするファイルの内容(文字列) */
function http_build_form_data($boundary, $prms, $upload_name, $upload_str) {
$return_value = "";
/* 改行コード */
$cr = "\n";
/* 連想配列からキーを取得 */
$keys = array_keys($prms);
/* パラメータ群 */
for ( $i=0 ; $i < count($keys) ; $i++ ) {
$return_value .=
"--" . $boundary . $cr .
'Content-Disposition: form-data; name="' . $keys[$i] . '"' . $cr .
$cr .
$prms[$keys[$i]] . $cr;
}
/* 文字列 $upload_str をファイルとしてアップロード */
$return_value .=
"--" . $boundary . $cr .
'Content-Disposition: form-data; name="' . $upload_name . '"; ' .
'filename="' . $upload_name . '.txt"' . $cr .
"Content-Type: text/plain" . $cr .
$cr .
$upload_str . $cr;
/* 最後の境界線 */
$return_value .= "--" . $boundary . "--" . $cr;
return $return_value;
}
/* サーバのURL */
$url = 'http://skyserver.sdss3.org/dr8/en/tools/crossid/x_crossid.asp';
/* SQLステートメント */
$uquery = "SELECT " . "\n" . /* ←ここの改行は絶対必要 */
"p.objID, p.ra, p.dec, p.run, p.rerun, p.camcol, p.field, " .
"dbo.fPhotoTypeN(p.type) as type, ".
"p.modelMag_u, p.modelMag_g, p.modelMag_r, p.modelMag_i, p.modelMag_z " .
"FROM #upload u " .
"JOIN #x x ON x.up_id = u.up_id " .
"JOIN PhotoTag p ON p.objID = x.objID " .
"ORDER BY x.up_id";
/* 座標リスト */
$upload = "name ra dec\n" . /* ←これも省略しない */
"A1 195.2 2.5\n" .
"A2 194.5 2.6\n" .
"A3 193.6, 2.8\n";
/* サーバへ送信するデータを作成 */
$prms = array( 'searchType' => 'photo',
'photoScope' => 'nearPrim',
'photoUpType' => 'ra-dec',
'joinSpec' => 'off',
'radius' => '0.5',
'format' => 'csv',
'firstcol' => '1',
'uquery' => $uquery
);
/* ヘッダ情報と中身を設定 */
$boundary = 'PostgreSQL_is_the_Best-' . time();
$content = http_build_form_data($boundary, $prms, "upload", $upload);
$headers = array('Content-Type: multipart/form-data; boundary=' . $boundary,
'Content-Length: ' . strlen($content));
$options = array( 'http' => array( 'method' => 'POST',
'header' => implode("\r\n", $headers),
'content' => $content,
)
);
/* 結果の取得 */
$result = file_get_contents($url, false, stream_context_create($options));
echo $result;
?>
SDSS の SQL Search で Cross-ID を行なう
次のように UNION ALL
を使って,
SQL Search で Cross-ID をやってしまう方法もあります.
こちらの方法では,ターゲットごとに検索半径を変える事ができます.
SELECT n.name, o.*
FROM photoobj o,
(
SELECT 'A1' as name, dbo.fgetnearestobjideq(195.2, 2.5, 0.5) AS objid
UNION ALL
SELECT 'A2' as name, dbo.fgetnearestobjideq(194.5, 2.6, 0.5) AS objid
UNION ALL
SELECT 'A3' as name, dbo.fgetnearestobjideq(193.6, 2.8, 0.5) AS objid
) n
WHERE n.objid = o.objid
タイムアウト処理
- サーバが混んでいる時などは,応答が遅くなる事があります.
PHP はデフォルトのタイムアウト値が設定されており,
それにひっかかるとエラーが発生します.
タイムアウトには,
サーバに接続完了までの時間と,
ストリーム読み出し時の待機時間があります.
- サーバからのデータ取得時にタイムアウトを設定できる関数を用意してみました.
次の関数は,$filename
で指定したファイルや URL
の内容をすべて読み取り,返すものです.
タイムアウト値はそれぞれ
$timeout_open
, $timeout_read
(秒) で設定します.第4引数以降は省略可能です.
エラーの場合は,false
を返します
(同時に標準エラー出力にエラーを出します).
function my_file_get_contents($filename, $timeout_open, $timeout_read,
$use_include_path = false, $context = false)
{
$old = ini_set('default_socket_timeout', $timeout_open);
if ( $context === false ) {
$fp = fopen($filename, 'rb', $use_include_path);
}
else {
$fp = fopen($filename, 'rb', $use_include_path, $context);
}
ini_set('default_socket_timeout', $old);
if ( $fp === false ) {
fputs(STDERR,"ERROR: fopen() failed. Timeout?\n");
return false;
}
stream_set_timeout($fp, $timeout_read);
$res = stream_get_contents($fp);
$status = stream_get_meta_data($fp);
fclose($fp);
if ( $status['timed_out'] ) {
fputs(STDERR,"ERROR: Timeout in stream_get_contents() !\n");
return false;
}
if ( $status['eof'] != true ) {
fputs(STDERR,"ERROR: Stream was truncated in stream_get_contents() !\n");
return false;
}
return $res;
}
- 上記関数を,
file_get_contents()
のかわりに使うだけで,
タイムアウトを設定できます.
厳密な EOF の判定
スクリプトを走らせる時のエチケット