要約:PostGISデータをSVGで便利に使えるよう、JavaScriptによる操作を模索しています。とりあえず今日は描画範囲の自動設定とCSS等でのスタイル設定まで。下はChromium43、Firefox38による描画で、使用データは昨日取り込んだuDigのサンプルDBです。
Contents

実行環境、主な流れ
実行環境はWindows 7 32bit + PostgreSQL Portable 9.4.1 + PostGIS 2.1.5。これにpgAdminからクエリを投げ、SVGをローカルに出力。別途HTMLJavaScriptもローカルに準備しておき、SVGOBJECT要素のソースとして読み込み、JavaScriptで配置やスタイルを操作します。

データを見るクライアントはブラウザ。配置やスタイルの変更が、HTML(かJavaScript)の一部を変えるだけで可能となり、SVG書き換えや、クエリを変えての再実行が不要になりました。


PostGISによるSVG出力
下が、昨日取り込んだuDigのサンプルDB5テーブルと、これを一つのSVGにまとめて出力するクエリ例。出力先にはPostgreSQL実行ユーザの書き込み権限が必要です。ST_AsSVG関数でジオメトリがSVG用の属性値になるので、path等の要素名を付けてタグ化し、各テーブルを一つのg要素にまとめ。
example_geom2svg.sqlSelectRawtextBitbucket
COPY (
    SELECT text '<?xml version="1.0" encoding="UTF-8" ?>' ||
        '<svg xmlns="http://www.w3.org/2000/svg">'

    UNION ALL
    SELECT '<g id="border">' || string_agg('<path d="' ||
        ST_AsSVG(ST_Transform(the_geom, 3005),
            maxdecimaldigits := 0) || '"/>', '') || '</g>'
    FROM hoge.bc_border

    UNION ALL
    SELECT '<g id="hospitals">' || string_agg('<ellipse ' ||
        ST_AsSVG(the_geom,
            maxdecimaldigits := 0) || '/>', '') || '</g>'
    FROM hoge.bc_hospitals

    UNION ALL
    SELECT '<g id="municipality">' || string_agg('<path d="' ||
        ST_AsSVG(the_geom,
            maxdecimaldigits := 0) || '"/>', '') || '</g>'
    FROM hoge.bc_municipality
    
    UNION ALL
    SELECT '<g id="pubs">' || string_agg('<ellipse ' ||
        ST_AsSVG(the_geom,
            maxdecimaldigits := 0) || '/>', '') || '</g>'
    FROM hoge.bc_pubs

    UNION ALL
    SELECT '<g id="voting_areas">' || string_agg('<path d="' ||
        ST_AsSVG(the_geom,
            maxdecimaldigits := 0) || '"/>', '') || '</g>'
    FROM hoge.bc_voting_areas

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

テーブルbc_borderだけSRIDが違い、上記クエリ内でST_Transform関数で変換。ST_AsSVG関数の引数のうちmaxdecimaldigitsは文字通り小数点以下の桁数で、今回のデータはメートル単位なので
(EPSG:3005, NAD83)ゼロに設定。もっと粗くてもいいですが、この引数で丸められるのは小数部分のみ。あと、点データをSVGcircle要素でなくellipse要素(楕円)にしているのは、縦横比調整への備え。

↓ 出力されたSVG、約12MB。参考までダウンロードは
こちらから。海岸線や行政界(選挙区)が細かく、クエリ実行に約1.2秒かかりました。まだ全体のサイズ情報や各要素の塗り・線がないため、単独では何も表示されません(二つ目の画像)。前はそれらの設定をクエリに含めていて、サイズや塗りなどを少し変えるにもクエリ再実行(SVG再作成)が必要で面倒だな~と思っていました。


JavaScriptによるSVG操作
今回、二つのクラスSvgBbox、SvgStyleを作りました。前者は全体のサイズ(SVGのビューポート)を自動設定するもの。その際、基準となる要素をIDで指定したり、必要に応じて横幅とかマージン等のオプションを付加できます。後者は、IDで指定された要素の塗り、線、点の大きさ等を主にCSSで設定(楕円の大きさはCSSで設定できないため、DOMツリーから)。下記が二つのソースコード。
/* coding: utf-8 */

var SvgBbox = function (svg) {
    this.svg = svg;
    this.width = document.body.clientWidth;
    this.height = null;
    this.fringe = 0;
    this.scale1px = null;
    this.vaspOption = 1;
};

SvgBbox.prototype.calc = function (id) {
    var chd = this.svg.getElementById(id).childNodes,
        aryX = Array(),
        aryY = Array();

    for (var i = 0, len = chd.length; i < len; i++) {
        var ele = chd[i],
            tgn = ele.tagName;

        if (tgn === 'ellipse') {
            aryX.push(Number(ele.getAttribute('cx')));
            aryY.push(Number(ele.getAttribute('cy')));

        } else if (tgn === 'text') {
            aryX.push(Number(ele.getAttribute('x')));
            aryY.push(Number(ele.getAttribute('y')));

        } else if (tgn === 'path') {
            var seg = ele.pathSegList;
            for (var j = 0, len2 = seg.length; j < len2; j++) {
                var s = seg[j];
                if (s.pathSegType === 2 || s.pathSegType === 4) {
                    // only SVGPathSegMovetoAbs or SVGPathSegLinetoAbs
                    aryX.push(s.x);
                    aryY.push(s.y);
                }
            }
        }
    }

    var x1 = Math.min.apply(null, aryX),
        y1 = Math.min.apply(null, aryY),
        x2 = Math.max.apply(null, aryX),
        y2 = Math.max.apply(null, aryY),
        hh = y2 - y1,
        ww = x2 - x1;

    this.scale1px = ww / this.width;
    this.height = hh / this.scale1px;

    var fgx = this.fringe * this.scale1px,
        fgy = this.fringe * this.scale1px / this.vaspOption;
        fg2x = this.fringe * 2,
        fg2y = this.fringe * 2 / this.vaspOption;

    this.svg.setAttribute('preserveAspectRatio', 'none');
    this.svg.setAttribute('height', this.height * this.vaspOption + fg2y);
    this.svg.setAttribute('width', this.width + fg2x);
    this.svg.setAttribute('viewBox', Array(
        x1 - fgx, y1 - fgy, x2 - x1 + fgx * 2, y2 - y1 + fgy * 2).join(' '));
};
geomSvgStyle.jsSelectRawtextBitbucket
/* coding: utf-8 */

var SvgStyle = function (svg, bbox) {
    this.svg = svg;
    this.bb = bbox;
};

SvgStyle.prototype.setTop = function (id) {
    var g = this.svg.getElementById(id),
        p = g.parentNode;
    p.removeChild(g);
    p.appendChild(g);
};

SvgStyle.prototype.setStrokeWidth = function (id, num) {
    var g = this.svg.getElementById(id),
        chd = g.childNodes,
        len = chd.length;
    for (var i = 0; i < len; i++) {
        chd[i].setAttribute('vector-effect', 'non-scaling-stroke');
    }
    g.setAttribute('stroke-width', num);
};

SvgStyle.prototype.setRadius = function (id, num, originalScale) {
    var els = this.svg.getElementById(id).getElementsByTagName('ellipse'),
        len = els.length,
        rx = num,
        ry = num / this.bb.vaspOption;
    if (! originalScale) {
        rx *= this.bb.scale1px;
        ry *= this.bb.scale1px;
    }
    for (var i = 0; i < len; i++) {
        var e = els[i];
        e.setAttribute('rx', rx);
        e.setAttribute('ry', ry);
    }
};

SvgStyle.prototype.css = function (text) {
    var ss = this.svg.parentNode.styleSheets;
    if (ss.length === 0) {
        this.svg.appendChild(document.createElementNS(
            'http://www.w3.org/2000/svg', 'style'));
    }
    ss[0].insertRule(text, 0);
};


最後に ↓ 表示用のHTML。二つのクラスとSVGを読み込み、各種設定をJavaScriptで記述。この部分も外部ファイルにするとか、表示画面に変更用フォームを置く等も考えられます。
example_view_svg.htmlSelectRawtextBitbucket
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8">
<style><!--
    body {
        margin: 0;
    }
    object {
        background: whitesmoke;
    }
    div.caption {
        margin: 5px 10px;
    }
--></style>
</head>
<body>

<object data="test.svg"></object>
<div class="caption">geomSVG example</div>

<script src="geomSvgBbox.js"></script>
<script src="geomSvgStyle.js"></script>
<script><!--
window.addEventListener('load', function () {
    var obj = document.getElementsByTagName('object')[0],
        svg = obj.contentDocument.documentElement,
        b = new SvgBbox(svg),
        s = new SvgStyle(svg, b);

    b.width = 500; // 表示横幅. 無指定ならブラウザ横幅
    b.fringe = 10; // 周縁ピクセル数. 無指定ならゼロ
    // b.vaspOption = 1.3; // 横に対する縦の補正比率
    b.calc('border');

    s.setTop('municipality');
    s.setTop('border');
    s.setTop('pubs');
    s.setTop('hospitals');
    s.setStrokeWidth('hospitals', 1);
    s.setStrokeWidth('border', 1);
    s.setStrokeWidth('voting_areas', 0.1);
    s.setRadius('hospitals', 4);
    s.setRadius('pubs', 40000, true); // 第3引数 真なら座標値で指定
    
    s.css('#border { fill: none; stroke: lightblue }');
    s.css('#hospitals { fill: white; stroke: gray }');
    s.css('#municipality { fill: brown }');
    s.css('#pubs { fill: green; opacity: 0.5 }');
    s.css('#voting_areas { fill: wheat; stroke: gray }');
});
//--></script>
</body>
</html>


以上まとめると ↓4つのファイルとなり、同じ場所に置いてHTMLをブラウザで開くと、SVGの地理データが可視化されます(二つ目の画像、冒頭の再掲)。とりあえずChormium43、Firefox38で表示確認しましたが、詳しく見ていないので明日に続きます。あと課題なども。