geoJSONオブジェクトを指定して、マップをd3の中央に配置します


140

現在d3では、描画するgeoJSONオブジェクトがある場合、それをスケーリングし、必要なサイズに変換して中央に配置する必要があります。これは非常に退屈な試行錯誤の仕事であり、これらの値を取得するためのより良い方法を誰かが知っているかどうか疑問に思っていましたか?

たとえば、このコードがある場合

var path, vis, xy;
xy = d3.geo.mercator().scale(8500).translate([0, -1200]);

path = d3.geo.path().projection(xy);

vis = d3.select("#vis").append("svg:svg").attr("width", 960).attr("height", 600);

d3.json("../../data/ireland2.geojson", function(json) {
  return vis.append("svg:g")
    .attr("class", "tracts")
    .selectAll("path")
    .data(json.features).enter()
    .append("svg:path")
    .attr("d", path)
    .attr("fill", "#85C3C0")
    .attr("stroke", "#222");
});

少しずつ行かずに.scale(8500)と.translate([0、-1200])を取得するにはどうすればよいですか?


回答:


134

以下は、ほぼあなたが望むことをするようです。スケーリングは問題ないようです。それを私の地図に適用すると、小さなオフセットがあります。この小さなオフセットが発生するのは、マップを中央に配置するために移動コマンドを使用しているためと考えられますが、中央コマンドを使用する必要があります。

  1. プロジェクションとd3.geo.pathを作成する
  2. 現在の投影の境界を計算します
  3. これらの境界を使用して、スケールと移動を計算します
  4. 投影を再作成します

コードで:

  var width  = 300;
  var height = 400;

  var vis = d3.select("#vis").append("svg")
      .attr("width", width).attr("height", height)

  d3.json("nld.json", function(json) {
      // create a first guess for the projection
      var center = d3.geo.centroid(json)
      var scale  = 150;
      var offset = [width/2, height/2];
      var projection = d3.geo.mercator().scale(scale).center(center)
          .translate(offset);

      // create the path
      var path = d3.geo.path().projection(projection);

      // using the path determine the bounds of the current map and use 
      // these to determine better values for the scale and translation
      var bounds  = path.bounds(json);
      var hscale  = scale*width  / (bounds[1][0] - bounds[0][0]);
      var vscale  = scale*height / (bounds[1][1] - bounds[0][1]);
      var scale   = (hscale < vscale) ? hscale : vscale;
      var offset  = [width - (bounds[0][0] + bounds[1][0])/2,
                        height - (bounds[0][1] + bounds[1][1])/2];

      // new projection
      projection = d3.geo.mercator().center(center)
        .scale(scale).translate(offset);
      path = path.projection(projection);

      // add a rectangle to see the bound of the svg
      vis.append("rect").attr('width', width).attr('height', height)
        .style('stroke', 'black').style('fill', 'none');

      vis.selectAll("path").data(json.features).enter().append("path")
        .attr("d", path)
        .style("fill", "red")
        .style("stroke-width", "1")
        .style("stroke", "black")
    });

9
ヤンファンデアランさん、この返事には決して感謝しませんでした。ちなみにこれは、もし私がバウンティを分割できるとしたら、本当に良い反応です。ありがとう!
2013

これを適用すると、bounds = infinityになります。これをどのように解決できるかについてのアイデアはありますか?
Simke Nys、2015

ここに述べたようにこれは同じ問題であるかもしれない@SimkeNys stackoverflow.com/questions/23953366/...そこが言及したソリューションをお試しください。
Jan van der Laan、2015

1
こんにちは、Jan、コードをありがとう。いくつかのGeoJsonデータを使用して例を試しましたが、機能しませんでした。私が間違っていることを教えてもらえますか?:)私はにGeoJSONデータをアップロード:onedrive.live.com/...
user2644964

1
D3 v4では、プロジェクションフィッティングは組み込みメソッドです:projection.fitSize([width, height], geojson)API docs)-以下の@dnltskの回答を参照してください。
Florian Ledermann、2018年

173

私の答えはJan van der Laanの答えに近いですが、地理的重心を計算する必要がないため、少し単純化することができます。必要なのは境界ボックスだけです。また、スケーリングされていない、変換されていない単位投影を使用することで、計算を簡略化できます。

コードの重要な部分はこれです:

// Create a unit projection.
var projection = d3.geo.albers()
    .scale(1)
    .translate([0, 0]);

// Create a path generator.
var path = d3.geo.path()
    .projection(projection);

// Compute the bounds of a feature of interest, then derive scale & translate.
var b = path.bounds(state),
    s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
    t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

// Update the projection to use computed scale & translate.
projection
    .scale(s)
    .translate(t);

単位投影でフィーチャの境界ボックスを比較した後、境界ボックスの縦横比(および)をキャンバスの縦横比(および)と比較して、適切なスケールを計算できます。この場合、バウンディングボックスを100%ではなく、キャンバスの95%にスケーリングしたので、ストロークと周囲の機能またはパディングのために、エッジに少し余分なスペースがあります。b[1][0] - b[0][0]b[1][1] - b[0][1]widthheight

次に、あなたが計算できる変換バウンディングボックス(の中央使用(b[1][0] + b[0][0]) / 2して(b[1][1] + b[0][1]) / 2)、キャンバスの中央を(width / 2およびheight / 2)を。境界ボックスは単位投影の座標内にあるため、スケール(s)を掛ける必要があることに注意してください。

たとえば、bl.ocks.org / 4707858

バウンディングボックスへのプロジェクト

投影を調整せずにコレクション内の特定のフィーチャにズームする方法、つまり、投影を幾何学的変換と組み合わせてズームインおよびズームアウトする方法については、関連する質問があります。これは上記と同じ原理を使用していますが、幾何学的変換(SVGの「変換」属性)が地理的投影と組み合わされているため、計算は少し異なります。

たとえば、bl.ocks.org / 4699541

境界ボックスにズーム


2
上記のコード、特に境界のインデックスにいくつかのエラーがあることを指摘したいと思います。s =(0.95 / Math.max((b [1] [0]-b [0] [0])/幅、(b [1] [1]-b [0] [0] )/高さ))* 500、t = [(幅-s *(b [1] [0] + b [0] [0]))/ 2、(高さ-s *(b [1] [1] + b [0] [1]))/ 2];
iros 2013年

2
@iros- * 500はここでは無関係であるように見えます...また、スケール計算に含まれているb[1][1] - b[0][0]はずb[1][1] - b[0][1]です。
nrabinowitz 2013


5
So:b.s = b[0][1]; b.n = b[1][1]; b.w = b[0][0]; b.e = b[1][0]; b.height = Math.abs(b.n - b.s); b.width = Math.abs(b.e - b.w); s = .9 / Math.max(b.width / width, (b.height / height));
ハーブコーディル2013

3
このようなコミュニティのおかげで、D3を使うのはとても楽しいです。驚くばかり!
arunkjn 14

54

d3 v4またはv5を使用すると、その方法が簡単になります。

var projection = d3.geoMercator().fitSize([width, height], geojson);
var path = d3.geoPath().projection(projection);

そして最後に

g.selectAll('path')
  .data(geojson.features)
  .enter()
  .append('path')
  .attr('d', path)
  .style("fill", "red")
  .style("stroke-width", "1")
  .style("stroke", "black");

楽しんで、乾杯


2
この回答がもっと投票されることを願っています。d3v4しばらく作業していて、この方法を発見しました。
マーク

2
どこgから来たの?それはsvgコンテナーですか?
Tschallacka

1
Tschallacka g<g></g>タグにする必要があります
giankotarola 2017

1
残念なことですが、これははるかに下であり、2つの質の高い回答の後です。これを見逃すのは簡単で、他の答えよりも明らかに単純です。
カート

1
ありがとうございました。v5でも動作します!
Chaitanya Bangera

53

私はd3の初心者です-私はそれをどのように理解するかを説明しようとしますが、すべてが正しいかどうかはわかりません。

秘密は、いくつかの方法が地図作成空間(緯度、経度)で動作し、他の方法が直交座標空間(画面上のx、y)で動作することを知っています。地図作成空間(私たちの惑星)は(ほぼ)球形であり、デカルト空間(画面)は平坦です。一方を他方の上にマッピングするには、投影と呼ばれるアルゴリズムが必要です。この空間は短すぎて、魅力的なプロジェクションの主題や、球形を平面に変換するために地理的特徴をどのように歪めるかには深く入りません。角度を保存するように設計されているものや、距離を保存するように設計されているものなど、常に妥協点があります(マイクボストックには、膨大な数のサンプルがあります)。

ここに画像の説明を入力してください

d3では、投影オブジェクトには、マップ単位で指定された中央のプロパティ/セッターがあります。

projection.center([location])

centerが指定されている場合、投影の中心を指定された場所、経度と緯度の度数で構成される2要素の配列に設定し、投影を返します。中心が指定されていない場合、現在の中心を返します。デフォルトは「0°、0°」です。

ピクセル単位で与えられた平行移動もあります-投影の中心はキャンバスに対して相対的です:

Projection.translate([point])

ポイントが指定されている場合、投影の移動オフセットを指定された2要素配列[x、y]に設定し、投影を返します。ポイントが指定されていない場合、現在の移動オフセットを返します。デフォルトは[480、250]です。平行移動オフセットは、投影の中心のピクセル座標を決定します。デフォルトの平行移動オフセットでは、960×500の領域の中心に「0°、0°」が配置されます。

キャンバスでフィーチャを中央に配置する場合、投影の中心をフィーチャバウンディングボックスの中央に設定します。これは、メルカトルを使用するときに機能しますは、私の国(ブラジル)で(WGS 84、グーグルマップで使用)を使用する。他の投影法と半球を使用してテストしたことはありません。他の状況に合わせて調整を行う必要があるかもしれませんが、これらの基本原則を順守すれば問題ありません。

たとえば、プロジェクションとパスが与えられた場合:

var projection = d3.geo.mercator()
    .scale(1);

var path = d3.geo.path()
    .projection(projection);

boundsメソッドpath戻りバウンディングボックスをピクセル単位で。ピクセル単位のサイズとマップ単位のサイズを比較して、正しい縮尺を見つけるために使用します(0.95では、幅または高さの最適なサイズより5%のマージンが得られます)。ここでの基本的なジオメトリ、対角線上の反対側のコーナーを指定して長方形の幅/高さを計算します。

var b = path.bounds(feature),
    s = 0.9 / Math.max(
                   (b[1][0] - b[0][0]) / width, 
                   (b[1][1] - b[0][1]) / height
               );
projection.scale(s); 

ここに画像の説明を入力してください

d3.geo.boundsメソッドを使用して、マップ単位で境界ボックスを見つけます。

b = d3.geo.bounds(feature);

投影の中心をバウンディングボックスの中心に設定します。

projection.center([(b[1][0]+b[0][0])/2, (b[1][1]+b[0][1])/2]);

translateメソッドを使用して、マップの中心をキャンバスの中心に移動します。

projection.translate([width/2, height/2]);

これで、マップの中央に5%のマージンでズームした機能が表示されます。


どこかにbl.ocksはありますか?
Hugolpz 2013

申し訳ありません、bl.ocksや要旨はありません。何をしようとしていますか?クリックしてズームするようなものですか?それを公開すれば、私はあなたのコードを見ることができます。
Paulo Scardine、2013

Bostockの回答と画像には、bl.ocks.orgの例へのリンクがあり、コード全体をコピーエンジニアすることができます。仕事が終わりました。+1と素晴らしいイラストをありがとう!
Hugolpz 2013

4

ある中央)(緯度/経度のペアを受け入れ、使用できる方法が。

私の理解から、translate()は文字どおりマップのピクセルを移動するためにのみ使用されます。スケールの決定方法はわかりません。


8
TopoJSONを使用していて、マップ全体を中央に配置したい場合は、-bboxを指定してtopojsonを実行し、JSONオブジェクトにbbox属性を含めることができます。中心の緯度/経度座標は、[(b [0] + b [2])/ 2、(b [1] + b [3])/ 2]である必要があります(bはbbox値です)。
Paulo Scardine 2013年


2

私はインターネットを中心に地図を中央に配置する手間のかからない方法を探していて、Jan van der Laanとmbostockの答えに触発されました。svgのコンテナーを使用している場合、jQueryを使用する簡単な方法を次に示します。パディング/ボーダーなどのために95%のボーダーを作成しました。

var width = $("#container").width() * 0.95,
    height = $("#container").width() * 0.95 / 1.9 //using height() doesn't work since there's nothing inside

var projection = d3.geo.mercator().translate([width / 2, height / 2]).scale(width);
var path = d3.geo.path().projection(projection);

var svg = d3.select("#container").append("svg").attr("width", width).attr("height", height);

正確なスケーリングを探している場合、この答えはうまくいきません。しかし、私のように、コンテナに集中化した地図を表示したい場合は、これで十分です。メルカトル図を表示しようとしたところ、この方法が地図の集中化に役立ち、南極部分が必要なくなったため、簡単に切り取ることができました。


1

マップをパン/ズームするには、リーフレットにSVGを重ねて表示する必要があります。これは、SVGを変換するよりもはるかに簡単です。この例http://bost.ocks.org/mike/leaflet/を参照してから、リーフレットのマップセンターを変更する方法


別の依存関係を追加することが懸念される場合は、パンとズームは、純粋なD3で簡単に行うことができ、参照stackoverflow.com/questions/17093614/...
サンパウロScardine

この回答は実際にはd3を扱っていません。d3でも地図をパン/ズームできます。リーフレットは必要ありません。(これが古い投稿であることに気づいただけで、答えを閲覧していただけです)
JARRRRG

0

mbostocksの回答とHerb Caudillのコメントにより、メルカトル図法を使用していた頃から、アラスカで問題が発生し始めました。私自身の目的のために、私は米国の州を計画し、中心にしようとしていることに注意する必要があります。次の例外を除いて、2つの回答をJan van der Laanの回答と結婚させなければならないことがわかりました。

  1. メルカトルに単純なプロジェクションを設定します。

    プロジェクション= d3.geo.mercator()。scale(1).translate([0,0]);

  2. パスを作成します。

    パス= d3.geo.path()。projection(projection);

3.境界を設定する:

var bounds = path.bounds(topoJson),
  dx = Math.abs(bounds[1][0] - bounds[0][0]),
  dy = Math.abs(bounds[1][1] - bounds[0][1]),
  x = (bounds[1][0] + bounds[0][0]),
  y = (bounds[1][1] + bounds[0][1]);

4.アラスカと半球に重なる州の例外を追加します。

if(dx > 1){
var center = d3.geo.centroid(topojson.feature(json, json.objects[topoObj]));
scale = height / dy * 0.85;
console.log(scale);
projection = projection
    .scale(scale)
    .center(center)
    .translate([ width/2, height/2]);
}else{
scale = 0.85 / Math.max( dx / width, dy / height );
offset = [ (width - scale * x)/2 , (height - scale * y)/2];

// new projection
projection = projection                     
    .scale(scale)
    .translate(offset);
}

これがお役に立てば幸いです。


0

垂直方向と水平方向を調整したい人のために、ここに解決策があります:

  var width  = 300;
  var height = 400;

  var vis = d3.select("#vis").append("svg")
      .attr("width", width).attr("height", height)

  d3.json("nld.json", function(json) {
      // create a first guess for the projection
      var center = d3.geo.centroid(json)
      var scale  = 150;
      var offset = [width/2, height/2];
      var projection = d3.geo.mercator().scale(scale).center(center)
          .translate(offset);

      // create the path
      var path = d3.geo.path().projection(projection);

      // using the path determine the bounds of the current map and use 
      // these to determine better values for the scale and translation
      var bounds  = path.bounds(json);
      var hscale  = scale*width  / (bounds[1][0] - bounds[0][0]);
      var vscale  = scale*height / (bounds[1][1] - bounds[0][1]);
      var scale   = (hscale < vscale) ? hscale : vscale;
      var offset  = [width - (bounds[0][0] + bounds[1][0])/2,
                        height - (bounds[0][1] + bounds[1][1])/2];

      // new projection
      projection = d3.geo.mercator().center(center)
        .scale(scale).translate(offset);
      path = path.projection(projection);

      // adjust projection
      var bounds  = path.bounds(json);
      offset[0] = offset[0] + (width - bounds[1][0] - bounds[0][0]) / 2;
      offset[1] = offset[1] + (height - bounds[1][1] - bounds[0][1]) / 2;

      projection = d3.geo.mercator().center(center)
        .scale(scale).translate(offset);
      path = path.projection(projection);

      // add a rectangle to see the bound of the svg
      vis.append("rect").attr('width', width).attr('height', height)
        .style('stroke', 'black').style('fill', 'none');

      vis.selectAll("path").data(json.features).enter().append("path")
        .attr("d", path)
        .style("fill", "red")
        .style("stroke-width", "1")
        .style("stroke", "black")
    });

0

機能を引き出す必要があるTopojsonを中心に配置する方法:

      var projection = d3.geo.albersUsa();

      var path = d3.geo.path()
        .projection(projection);


      var tracts = topojson.feature(mapdata, mapdata.objects.tx_counties);

      projection
          .scale(1)
          .translate([0, 0]);

      var b = path.bounds(tracts),
          s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
          t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

      projection
          .scale(s)
          .translate(t);

        svg.append("path")
            .datum(topojson.feature(mapdata, mapdata.objects.tx_counties))
            .attr("d", path)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.