145

現在、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])を取得するにはどうすればよいですか?

4

11 に答える 11

177

私の答えはJanvander 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]) / 2width / 2height / 2s

例: bl.ocks.org/4707858

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

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

例: bl.ocks.org/4699541

バウンディングボックスにズーム

于 2013-02-04T17:11:52.703 に答える
138

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

  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")
    });
于 2013-02-01T21:02:20.663 に答える
64

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");

お楽しみください、乾杯

于 2016-12-02T20:02:38.617 に答える
54

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

秘密は、いくつかの方法が地図作成空間(緯度、経度)で動作し、他の方法がデカルト空間(画面上のx、y)で動作することを知っていることです。地図作成空間(私たちの惑星)は(ほぼ)球形であり、デカルト空間(画面)は平坦です-相互にマッピングするには、投影と呼ばれるアルゴリズムが必要です。この空間は短すぎて、投影の魅力的な主題と、球形を平面に変えるためにそれらが地理的特徴をどのように歪めるかを深く掘り下げることはできません。角度を節約するように設計されているものもあれば、距離を節約するように設計されているものもあります。常に妥協点があります(Mike Bostockには膨大な数の例があります)。

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

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

Projection.center([場所])

centerが指定されている場合、投影の中心を指定された位置に設定します。これは、経度と緯度の2要素配列(度単位)であり、投影を返します。centerが指定されていない場合、現在の中心を返します。デフォルトは⟨0°、0°⟩です。

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

Projection.translate([point])

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

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

たとえば、投影とパスが与えられた場合:

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

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

boundsfromメソッドは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%のマージンでズームされているはずです。

于 2013-06-12T13:54:56.363 に答える
4

lat / lonペアを受け入れる使用できるcenter()メソッドがあります。

私の理解では、translate()は、マップのピクセルを文字通り移動するためにのみ使用されます。スケールが何であるかを判断する方法がわかりません。

于 2013-01-29T21:31:08.167 に答える
3

geoJSONオブジェクトを指定してd3でマップを中央に配置することに加えて、オブジェクトの境界の周りにパディングを指定する場合は、より優先fitExtent()する場合があることに注意してください。このパディングを自動的に0に設定します。fitSize()fitSize()

于 2018-04-01T12:07:15.293 に答える
2

私はインターネットで地図を中央に配置するための手間のかからない方法を探していましたが、ヤンファンデルラーンと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);

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

于 2013-06-10T08:24:42.723 に答える
1

マップをパン/ズームするには、リーフレットにSVGをオーバーレイすることを確認する必要があります。これは、SVGを変換するよりもはるかに簡単です。この例http://bost.ocks.org/mike/leaflet/を参照してから、リーフレットのマップの中心を変更する方法を参照してください。

于 2013-01-31T22:25:45.343 に答える
0

mbostocksの回答と、Herb Caudillのコメントにより、メルカトル図法を使用していたため、アラスカで問題が発生し始めました。私自身の目的のために、私は米国の州を計画し、中心に据えようとしていることに注意する必要があります。半球と重なるポリゴン(1より大きい東西の絶対値になるポリゴン)を除いて、2つの回答をヤンファンデルラーンの回答と組み合わせる必要があることがわかりました。

  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);
}

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

于 2015-11-03T15:36:03.877 に答える
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")
    });
于 2016-02-13T12:01:49.967 に答える
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)
于 2016-03-31T21:18:44.260 に答える