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

다음은 대략 원하는 것을하는 것 같습니다. 스케일링은 괜찮은 것 같습니다. 내지도에 적용 할 때 작은 오프셋이 있습니다. 이 작은 오프셋은지도를 가운데에 맞추기 위해 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")
    });

9
Jan van der Laan이 응답에 대해 결코 감사하지 않았습니다. 내가 현상금을 분리 할 수 ​​있다면 이것은 정말 좋은 반응입니다. 감사합니다!
climboid

이것을 적용하면 범위 = 무한대를 얻습니다. 이것이 어떻게 해결할 수 있는지에 대한 아이디어가 있습니까?
Simke Nys

@SimkeNys 이것은 여기에 언급 된 것과 동일한 문제 일 수 있습니다. stackoverflow.com/questions/23953366/… 여기에 언급 된 해결책을 시도하십시오.
Jan van der Laan

1
Jan Jan, 코드 감사합니다. GeoJson 데이터로 예제를 시도했지만 작동하지 않았습니다. 내가 뭘 잘못하고 있는지 말해 줄래? :) GeoJson 데이터를 업로드했습니다 : onedrive.live.com/…
user2644964

1
D3 v4 프로젝션 피팅은 내장 된 방법입니다 : projection.fitSize([width, height], geojson)( API docs )-아래 @dnltsk의 답변을 참조하십시오.
Florian Ledermann

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

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

경계 상자로 투영

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

예를 들어, 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

2
@iros- * 500여기서는 이질적인 것 같습니다 ... 또한 스케일 계산에 b[1][1] - b[0][0]있어야합니다 b[1][1] - b[0][1].
nrabinowitz


5
그래서 :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));
허브 Caudill

3
이와 같은 커뮤니티 덕분에 D3는 함께 일하는 기쁨입니다. 대박!
arunkjn

55

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한동안 일하면서이 방법을 발견했습니다.
Mark

2
어디 g에서 왔습니까? 그게 svg 컨테이너입니까?
Tschallacka 2016 년

1
Tschallacka는 g해야 <g></g>태그
giankotarola

1
부끄러운 일이지만 지금까지 2 품질 답변 후입니다. 이것을 놓치기 쉽고 다른 답변보다 훨씬 간단합니다.
커트

1
감사합니다. v5에서도 작동합니다!
차이 타이 방 게라

53

나는 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 %의 마진으로 확대되었습니다.


어딘가에 bl.ocks가 있습니까?
Hugolpz

죄송합니다. bl.ocks 또는 gist가 없습니다. 무엇을하려고하십니까? 클릭 투 줌과 같은 것입니까? 그것을 게시하면 코드를 살펴볼 수 있습니다.
Paulo Scardine

Bostock의 답변과 이미지 는 bl.ocks.org 예제에 대한 링크를 제공하여 전체 코드를 복사 엔지니어로 만들 수 있습니다. 작업이 완료되었습니다. +1하고 멋진 일러스트에 감사드립니다!
Hugolpz

4

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

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


8
TopoJSON을 사용하고 전체 맵을 중앙에 배치하려는 경우 --bbox와 함께 topojson을 실행하여 JSON 객체에 bbox 속성을 포함시킬 수 있습니다. 중심의 위도 / 경도 좌표는 [(b [0] + b [2]) / 2, (b [1] + b [3]) / 2] 여야합니다 (b는 bbox 값).
Paulo Scardine


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

지도를 이동 / 확대하려면 Leaflet에서 SVG를 오버레이해야합니다. SVG를 변환하는 것보다 훨씬 쉽습니다. 이 예를 참조하십시오 http://bost.ocks.org/mike/leaflet/ 그리고 전단지에서지도 센터를 변경하는 방법


또 다른 의존성을 추가하는 것은 문제의 경우, PAN 및 ZOOM 순수한 D3 쉽게 수행 할 수 있습니다, 참조 stackoverflow.com/questions/17093614/...
파울로 Scardine

이 답변은 실제로 d3를 다루지 않습니다. d3에서도지도를 이동 / 확대 할 수 있으며 전단지는 필요하지 않습니다. (이것은 오래된 게시물을 깨달았고 답을 탐색하는 중이었습니다)
JARRRRG

0

mbostocks의 답변과 Herb Caudill의 의견으로, 나는 메르카토르 투영을 사용한 이후 알래스카에 문제가 발생하기 시작했습니다. 본인의 목적을 위해 미국을 계획하고 중심으로 삼고 있습니다. 반구를 겹치는 다각형 (동서 서쪽의 절대 값이 1보다 큰 다각형)을 제외하고 Jan van der Laan 답변으로 두 가지 대답을 결혼해야한다는 것을 알았습니다.

  1. 메르카토르에서 간단한 투영을 설정하십시오.

    projection = 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.