要約:昨日始めた新しい処理方法のうち、PostGISジオメトリのSVG出力について。点データはellipse要素に、線・面データはpath要素にして、塗りなどのスタイルは入れません。スタイル設定をJavaScriptに任せることでSVG出力クエリを簡略化できる一方、SVG単独では何も表示されないのがデメリット。(下図は昨日の再掲)
Contents

以前の方法
ジオメトリをSVGで可視化するには(1)全体の描画サイズ・範囲(2)個々の地物の地理データ(3)個々の地物の塗り・線、という3つの情報が必要。以前は全てをSQLに入れて「完成形」のSVGデータを作り、COPYコマンドでファイル出力していました。

(1)はST_Envelope関数で全体の範囲を取得し、表示させたいサイズと合わせてビューポートを算出。(3)のうち線幅も、表示サイズや地理座標の大きさから算出する式をSQLに入れるという具合。表示サイズ・範囲や塗り・線を変えるたび、SQL変更とクエリ再実行が必要になって面倒でした。


今回の方法
上記(1)と(3)をJavaScriptから事後的に設定する方針に変え、SVGには必要最低限の情報、つまり(2)地理データと、地物またはそのグループのIDだけを入れるようにしました。後者は、表示サイズ・範囲の基準とする地物を決めたり、塗り・線を地物(グループ)別に設定するのに使います。

これで、SVG出力クエリが ↓ のように比較的に簡単に。先頭行にXML宣言とSVG開始タグ、最終行にSVG終了タグ。その間に任意のジオメトリを、点はellispe要素(楕円)として、その他はpath要素として挿入。任意のグループ別に(例えば同じテーブルの地物など)一つのg要素にまとめ、IDを付けます。

template_geom2svg.sqlSelectRawtextBitbucket
COPY (
    SELECT text '<?xml version="1.0" encoding="UTF-8" ?>' ||
        '<svg xmlns="http://www.w3.org/2000/svg">'

    -- point
    UNION ALL
    SELECT '<g id="my_points">' || string_agg('<ellipse ' ||
        ST_AsSVG(geom) || '/>', '') || '</g>'
    FROM geometry_table

    -- (multi)linestring or (multi)polygon
    UNION ALL
    SELECT '<g id="my_lines">' || string_agg('<path d="' ||
        ST_AsSVG(geom) || '"/>', '') || '</g>'
    FROM geometry_table

    UNION ALL
    SELECT '</svg>'
) TO 'R:/my_output.svg';

COPY ... TOの出力先は絶対パスで、そこにPostgreSQLユーザの書き込み権限が必要です。下が出力結果の例で(
昨日の再掲)、この時点では表示サイズや各要素の塗り・線がないので、ブラウザ等で開いても、 二つ目の画像のように何も表示されません。


点をcircleでなくellipse要素(楕円)にする理由
メートル単位の地理データなど、縦横比を変える必要がなければcircleの方が簡単でいいです。経緯度データの場合、赤道から離れるほど「縦1°当りの実距離>1°当りの実距離」になるので、そのまま描画するより、縦横比を調整して簡易補正する方がベター。この時、circle要素は縦横同じ半径しか取れないため結果的に楕円になり、一方ellipse要素なら縦横比を逆に掛けて円にできます。

点が小さければ余り意味のない、細かい話です。そもそもデータ自体をメートル単位の座標系に変換して描画する方が、実際に近い正攻法な手段
(昨日はそうした)。ただ、後で別の経緯度データを乗せたりするには経緯度データ+簡易補正で作っておく方が便利で、そんな時に備えてellipse要素にしています。


Multipoint、GeometryCollectionはそのままでは使えない
前のブログ(PostGISジオメトリコレクションをSVGに変換する時の注意、2015.01.19)と同じ件。PostGISST_AsSVG関数はマニュアルに ↓ 記載のとおりMultipointはカンマで、GeometryCollectionはセミコロンで地物を区切って属性値を出力しますが、この形式はSVGの要素に使えません。
Multipoint geometries are delimited by commas (","), GeometryCollection geometries are delimited by semicolons (";").

そもそも地理データの標準的な種別とSVG要素の種別が全然違うので仕方なく(pointに当たる要素もない)、クエリの時点でpoint、(multi)linestring、(multi)polygonのどれかに分けることにします。GeometryCollectionの処理例は、上記ブログ記事で少し書きました(ST_CollectionExtract関数で点・線・面のどれかに分けたり、ST_CollectionHomogenize関数でマルチジオメトリに変換)。


現在の制約、課題
1)SVGに相対座標を使えない。path要素のd属性の話です。PostGISST_AsSVG関数は相対座標でも出力できますが(引数rel=1を指定)、全体の範囲を確定するJavaScriptを、処理が簡単な絶対座標のみの想定で作ったため。今後、あえて相対座標を使う必要が出るor使えるライブラリが見つかったら考えますが、今のところクエリ側で絶対座標に固定。

2)SVG要素への変換をストアド関数化したい。以前に比べれば良くなったとは言え、まだSQLpath要素の作成、g要素へのグループ化といった面倒な、しかも可読性が悪い処理を毎回書いています。これをストアド関数化して簡単にしたいところ。普通の関数でなく集約関数の自作になりそう。

明日はSVG出力後の処理(JavaScript)について書きます。