はてだBlog(仮称)

私的なブログど真ん中のつもりでしたが、気づけばWebサイト系のアプリケーション開発周りで感じたこと寄りの自分メモなどをつれづれ述べています。2020年6月現在、Elasticsearch、pandas、CMSなどに関する話題が多めです。...ですが、だんだんとより私的なプログラムのスニペット置き場になりつつあります。ブログで述べている内容は所属組織で販売している製品などに関するものではなく、また所属する組織の見解を代表するものではありません。

なめらかGeoJSONぽいこと

GeoJSONですが、仕様と要件の性質上ある意味あたりまえだと思いますが、頂点をよろしく曲線で結ぶ仕様はないようです*1

でも、下記図みたいな、こんな感じのことをしたいこともあるよね?ということで調べてみて、d3.jsで試してみましたの例です。

f:id:azotar:20200429211821p:plain

あたかも独自のテクニックで、d3.jsを拡張したりしたかのような言い回しですが、実際は、以下のブログを参考にさせていただきました。

shimz.me

また、折れ線の曲線化にあたり、補間曲線のバリエーション例が豊富な次のブログを参考にさせていただきました。

wizardace.com

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script src="aichi.js"></script>
</head>
<body>
    <script>

        //外部からデータ読み込み
        const geojson = pref

        const width = 800,
            height = 800

        //svg初期化など
        const svg = d3.select("body").append("svg").attr("width", width).attr("height", height)

        //GeoJSONデータの頂点を滑らかな曲線で結ぶようなpathを生成する関数 ※GeoJSONのデータの入り方は分岐のない一筆書きの形式を前提にしている。万能ではない。
        const namerakaHenkan = (d) => { //https://shimz.me/blog/google-map-api/4618 さんのアイディアを参考にした
            console.log(d)
            //メルカトル図法でのgeoPathパスジェネレーター作成
            const scale = 16000
            const center = [137.341, 34.647]
            const proj = d3.geoMercator()
                .center(center)
                .translate([width / 2, height / 2])
                .scale(scale)
            const path = d3.geoPath().projection(proj)

            // GeoJSONデータから、それに対応したSVGのpathオブジェクトを生成〜座標の配列を取り出す
            const geopath = path(d)
            console.log(geopath)
            /*
                M400,400L325.4395343547949,394.90783940438087L333.2586094037324,405.09123940300924L348.3382541409592,397.6237730675293L370.9577212468066,384.381118750538L379.056048976061,379.2861289060111L388.5506401069142,384.381118750538L392.7394303116962,386.0792434899704L391.0639142297805,
                ...L439.65388060530677,391.1730024366243Z 
                のようなデータになっている。
            */
            const points = geopath.replace(/M|Z/, "").split("L").map((d) => d.split(",")) // SVGのpath命令となっている文字列から「点」の座標に該当する部分を抜き出す
            console.log(points)
            /*
                [   ["400", "400"],
                    ["325.4395343547949", "394.90783940438087"],
                    ["333.2586094037324", "405.09123940300924"],
                ...
                ]
                のように変換
            */

            // 折れ線情報(points)を表すx,y座標の配列を引数にとる、d3.line().curve()を使って、なめらかな曲線のSVGのpathを生成する
            let cuv = null
            cuv = d3.curveBasisClosed
            // 以下バリエーション 
            //cuv = d3.curveCardinalClosed.tension(0.9) 
            //cuv = d3.curveCatmullRom.alpha(0.9)
            //cuv = d3.curveCardinal.tension(0.9)
            //cuv = d3.curveNatural
            //cuv = d3.curveBasis 
            const interpolate = d3.line().x((d) => parseFloat(d[0])).y((d) => parseFloat(d[1])).curve(cuv)
            const namerakaPath = interpolate(points)
            console.log(namerakaPath)
            /*

            */
            return namerakaPath
        }

        const map = svg.append("g").selectAll("path")
            .data(geojson.features)
            .enter()
            .append("path")
            .attr('d', (d) => namerakaHenkan(d)) // なめらか変換のpath
            .style("stroke", "gray")
            .style("fill", "yellow")

        // おまけのZoom & Drug移動機能           
        const zoomeffect = () => {
            let t = d3.event.transform
            map
                .attr('transform', t)
                .attr('stroke-width', 1 / t.k)
        }

        svg.
            call(d3.zoom().on('zoom', zoomeffect))

    </script>

</body>
</html>

◆インプットにしたGeoJSONデータのファイル(aichi.js)

なんちゃって愛知県シェープです。 これを冒頭の図に示したように、なめらか愛知県シェープに変換して表示することになります。

let pref =
{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "MultiPolygon",
                "coordinates": [
                    [[
                        [137.341, 34.647],
                        [137.074, 34.662],
                        [137.102, 34.632],
                        [137.156, 34.654],
                        [137.237, 34.693],
                        [137.266, 34.708],
                        [137.300, 34.693],
                        [137.315, 34.688],
                        [137.309, 34.700],
                        [137.351, 34.727],
                        [137.311, 34.803],
                        [137.250, 34.805],
                        [137.214, 34.815],
                        [137.186, 34.792],
                        [137.168, 34.787],
                        [137.105, 34.791],
                        [136.996, 34.816],
                        [136.955, 34.854],
                        [136.976, 34.893],
                        [136.971, 34.908],
                        [136.934, 34.874],
                        [136.936, 34.854],
                        [136.921, 34.828],
                        [136.969, 34.725],
                        [136.976, 34.701],
                        [136.851, 34.748],
                        [136.837, 34.875],
                        [136.828, 34.961],
                        [136.869, 35.008],
                        [136.873, 35.071],
                        [136.884, 35.088],
                        [136.857, 35.059],
                        [136.843, 35.041],
                        [136.849, 35.077],
                        [136.838, 35.030],
                        [136.809, 35.049],
                        [136.780, 35.033],
                        [136.769, 35.021],
                        [136.677, 35.128],
                        [136.677, 35.241],
                        [136.864, 35.372],
                        [136.991, 35.424],
                        [137.047, 35.336],
                        [137.067, 35.308],
                        [137.102, 35.301],
                        [137.185, 35.253],
                        [137.345, 35.272],
                        [137.408, 35.234],
                        [137.520, 35.273],
                        [137.570, 35.282],
                        [137.576, 35.220],
                        [137.655, 35.228],
                        [137.759, 35.221],
                        [137.823, 35.211],
                        [137.823, 35.158],
                        [137.802, 35.130],
                        [137.778, 35.099],
                        [137.704, 34.972],
                        [137.639, 34.892],
                        [137.549, 34.843],
                        [137.496, 34.786],
                        [137.483, 34.673],
                        [137.340, 34.647]
                    ]]]

            }
        },
    ]
}

以上です。

このテーマはもう少し頑張ってアドホックだけど上手い(おいしい)方法が編み出せたらと思っていますが、ひとまずここまで。

付録:その他

私の主観ベースですが、d3.jsの基礎的なところは次の記事にまとめてあります。 また、本項にある d3.line()のもっとシンプルな使い方の例も入っています。

itdepends.hateblo.jp

*1:間違ってたらごめんなさい。