[d3.js] geoJSON 객체가 주어지면 d3에서지도 중앙에

현재 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])을 얻습니까?



답변

다음은 대략 원하는 것을하는 것 같습니다. 스케일링은 괜찮은 것 같습니다. 내지도에 적용 할 때 작은 오프셋이 있습니다. 이 작은 오프셋은지도를 가운데에 맞추기 위해 translate 명령을 사용하기 때문에 발생할 수 있으며, 가운데 명령을 사용해야 할 수도 있습니다.

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


답변

내 대답은 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 / 2height / 2). 경계 상자는 단위 투영 좌표에 있으므로 스케일 ( s)을 곱해야합니다 .

예를 들어, bl.ocks.org/4707858 :

경계 상자로 투영

투영을 조정하지 않고 컬렉션의 특정 기능을 확대하는 방법, 투영을 기하학적 변환과 결합하여 확대 및 축소 하는 방법에 대한 관련 질문이 있습니다 . 위와 동일한 원리를 사용하지만 기하학적 변환 (SVG “transform”속성)이 지리적 투영과 결합되므로 수학이 약간 다릅니다.

예를 들어, bl.ocks.org/4699541 :

경계 상자로 확대


답변

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

즐기세요, 건배


답변

나는 d3을 처음 사용합니다. 어떻게 이해하는지 설명하려고 노력할 것이지만 모든 것이 올바르게 이해되었는지는 확실하지 않습니다.

비밀은 일부 방법이지도 제작 공간 (위도, 경도)에서 작동하고 다른 방법은 데카르트 공간 (화면의 x, y)에서 작동한다는 것을 알고 있습니다. 지도 제작 공간 (우리의 행성)은 (거의) 구형이며, 데카르트 공간 (스크린)은 평평합니다. 서로 매핑하기 위해서는 투영 이라고하는 알고리즘이 필요합니다 . 이 공간은 너무나 짧아 매혹적인 투영 주제에 깊이 들어 가지 않고 구면을 평면으로 만들기 위해 지리적 특징을 왜곡하는 방법입니다. 일부는 각도를 유지하도록 설계되고 다른 일부는 거리 를 유지하는 등 항상 타협이 있습니다 (Mike Bostock에는 엄청난 수의 예제 모음이 있음 ).

여기에 이미지 설명을 입력하십시오

d3에서 투영 객체에는지도 단위로 지정된 중심 속성 / 세터가 있습니다.

projection.center ([위치])

center를 지정하면 투영의 중심을 지정된 위치 (경도 및 위도의도 단위로 배열)로 설정하고 투영을 반환합니다. center를 지정하지 않으면 현재 중심을 returns0 °, 0 °⟩로 반환합니다.

투영 중심이 캔버스를 기준으로하는 픽셀 단위의 변환도 있습니다.

projection.translate ([point])

point가 지정되면 투영의 변환 오프셋을 지정된 2 요소 배열 [x, y]로 설정하고 투영을 반환합니다. point를 지정하지 않으면 현재 변환 오프셋을 반환하며 기본값은 [480, 250]입니다. 평행 이동 오프셋은 투영 중심의 픽셀 좌표를 결정합니다. 기본 변환 오프셋은 960 × 500 영역의 중앙에 ⟨0 °, 0 °⟩를 배치합니다.

캔버스에서 피처를 가운데에 놓고 싶을 때 투영 중심을 피처 경계 상자의 가운데에 설정하고 싶습니다. 은 내 국가 (브라질)에 메르카토르 (Google지도에 사용 된 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 %의 마진으로 확대되었습니다.


답변

위도 / 경도 쌍을 허용 하는 center () 메서드를 사용할 수 있습니다.

내가 이해 한 것에서 translate ()은 문자 그대로 맵의 픽셀을 이동하는 데만 사용됩니다. 스케일이 어떻게 결정되는지 잘 모르겠습니다.


답변

geoJSON 객체가 지정된 d3에서지도중앙에 배치하는 것 외에도 객체 경계 주위에 패딩을 지정하려는 경우 fitExtent()보다 선호 할 수 있습니다 fitSize(). fitSize()이 패딩을 0으로 자동 설정합니다.


답변

인터넷을 통해지도를 중앙에 배치 할 수있는 방법을 찾고 있었으며 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);

정확한 스케일링을 찾으려면이 답변이 효과가 없습니다. 그러나 저처럼 컨테이너에 집중된 맵을 표시하려면 이것으로 충분합니다. 나는 메르카토르지도를 보여 주려고 노력했는데이 방법이지도를 중앙 집중화하는 데 유용하다는 것을 알았습니다. 남극 부분은 필요하지 않기 때문에 쉽게 잘라낼 수있었습니다.