【D3.js】世界地図画像をクライアントサイドで再投影処理する
正距円筒図法で作成された世界地図画像をクライアント側で他の投影法へ変換し出力します。
結構負荷が高いです。
サンプル
概要を簡単に説明すると、d3.geo.projectionの緯度経度⇔pixcel座標の変換機能を使って以下の処理を行ています。
1.投影法Aを使ってソース画像のpixcel座標をすべて緯度経度に変換。
2.変換した緯度経度を投影法Bを使ってターゲット画像のpixcelの座標に変換。
3.ソース画像のpixcel座標から色情報を取得し、ターゲット画像のpixcelの座標にコピーする。
4.ターゲット画像を出力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
d3.select("#projection").on("change", function(){ draw(this.value); }); var width = 540; var height = 270; var div = d3.select('#reprojection_output'); var canvas = div.append('canvas') .attr('width', width) .attr('height', height); var context = canvas.node().getContext('2d'); var sourceProjection = d3.geo.equirectangular() .scale(width / (2 * Math.PI)) .translate([width / 2, height / 2]); // 画像要素読み込み var image = new Image(width, height); image.onload = function(){ document.querySelector("#projection").dispatchEvent(new Event('change')); }; image.src = 'readme-blue-marble.jpg'; function draw(select_projection) { var targetProjection = d3.geo[select_projection]() .scale(Math.sqrt(1) * height / Math.PI ) .translate([width / 2, height / 2]) // キャンバスに画像をコピーする context.drawImage(image, 0, 0, image.width, image.height); // キャンバスからイメージデータを取得する var sourceData = context.getImageData(0, 0, image.width, image.height).data; //空のターゲットイメージを生成 var target = context.createImageData(image.width, image.height), targetData = target.data; // ターゲット画像をipixcel毎に変換処理を行う for (var x = 0, w = image.width; x < w; x += 1) { for (var y = 0, h = image.height; y < h; y += 1) { // 変換する投影法を用いてカレントピクセルの地理座標を計算 var coords = targetProjection.invert([x, y]); var targetIndex; var sourceIndex; var pixels; // 地理座標が未定義で返ってきた場合はスルー if ((!isNaN(coords[0])) && (!isNaN(coords[1]))) { //範囲外を塗りつぶさないようにとばす var λ = coords[0], φ = coords[1]; if (λ > 180 || λ < -180 || φ > 90 || φ < -90) { continue; } // 元の投影法を用いて地理座標を計算 pixels = sourceProjection (coords); // ソース&ターゲットピクセルのレッドチャネルのインデックスを計算 sourceIndex = 4 * (Math.floor(pixels[0]) + w * Math.floor(pixels[1])); sourceIndex = sourceIndex - (sourceIndex % 4); targetIndex = 4 * (x + w * y); targetIndex = targetIndex - (targetIndex % 4); //赤、緑、青、アルファチャンネルをコピーする targetData[targetIndex] = sourceData[sourceIndex]; //レッドチャンネル targetData[targetIndex + 1] = sourceData[sourceIndex + 1]; targetData[targetIndex + 2] = sourceData[sourceIndex + 2]; targetData[targetIndex + 3] = sourceData[sourceIndex + 3]; } } } // canvas要素をクリアして、対象画像をコピーする context.clearRect(0, 0, image.width, image.height); context.putImageData(target, 0, 0); } |
おまけ
NASAが提供している雲量地図画像でもやってみました。
Cloud Fraction (1 month – Terra/MODIS) | NASA