给定geoJSON对象,在d3中居中地图

时间:2013-01-24 01:11:10

标签: d3.js geojson topojson

目前在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])?

11 个答案:

答案 0 :(得分:163)

我的答案接近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);

在单位投影中对要素的bounding box进行复合后,您可以通过比较边界框({​​{1}}和{{1}的宽高比来计算相应的比例。 })到画布的宽高比(b[1][0] - b[0][0]b[1][1] - b[0][1])。在这种情况下,我还将边界框缩放到画布的95%,而不是100%,因此在笔迹和周围特征或填充的边缘上有一点额外的空间。

然后,您可以使用边框(widthheight)的中心和画布的中心((b[1][0] + b[0][0]) / 2和{来计算翻译 {1}})。请注意,由于边界框位于单位投影的坐标中,因此必须乘以比例((b[1][1] + b[0][1]) / 2)。

例如,bl.ocks.org/4707858

project to bounding box

有一个相关的问题,即如何在不调整投影的情况下缩放到集合中的特定特征,,将投影与几何变换相结合以放大和缩小。它使用与上述相同的原理,但数学略有不同,因为几何变换(SVG“变换”属性)与地理投影相结合。

例如,bl.ocks.org/4699541

zoom to bounding box

答案 1 :(得分:123)

以下似乎大概做了你想要的。缩放似乎没问题。将它应用到我的地图时会有一个小的偏移量。这个小偏移可能是因为我使用translate命令使地图居中,而我应该使用center命令。

  1. 创建投影和d3.geo.path
  2. 计算当前投影的界限
  3. 使用这些边界来计算比例和翻译
  4. 重新创建投影
  5. 在代码中:

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

答案 2 :(得分:49)

我是d3的新手 - 会尝试解释我是如何理解的,但我不确定我是否做得对。

秘密在于知道某些方法将在制图空间(纬度,经度)和笛卡尔空间(屏幕上的x,y)上进行操作。制图空间(我们的星球)是(几乎)球形,笛卡尔空间(屏幕)是平的 - 为了将一个映射到另一个,你需要一个算法,称为projection。这个空间太短,无法深入到投影的迷人主题中,以及它们如何扭曲地理特征以便将球形转变为平面; some are designed to conserve angles, others conserve distances等等 - 总是有妥协(Mike Bostock有huge collection of examples)。

enter image description here

在d3中,投影对象具有中心属性/ setter,以地图单位给出:

  

projection.center([位置])

     

如果指定了center,则将投影的中心设置为指定位置,以度为单位的经度和纬度的双元素数组并返回投影。如果未指定center,则返回当前中心,默认为⟨0°,0°⟩。

还有以像素为单位的平移 - 投影中心相对于画布的位置:

  

projection.translate([点])

     

如果指定了point,则将投影的平移偏移设置为指定的双元素数组[x,y]并返回投影。如果未指定point,则返回当前平移偏移,默认为[480,250]。平移偏移确定投影中心的像素坐标。默认平移偏移将⟨0°,0°places置于960×500区域的中心。

当我想在画布中居中显示某个要素时,我喜欢将投影中心设置为要素边界框的中心 - 这对我来说在使用mercator时很有用(WGS 84,用于谷歌地图)对于我的国家(巴西),从未使用其他投影和半球进行测试。你可能不得不为其他情况做出调整,但如果你指出这些基本原则,你就可以了。

例如,给定投影和路径:

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

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

来自bounds的{​​{1}}方法会返回以像素为单位的边界框。使用它来查找正确的比例,将像素的大小与地图单位的大小进行比较(0.95给出的宽度或高度最佳拟合度为5%)。这里的基本几何,计算对角线对角的矩形宽度/高度:

path

enter image description here

使用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

到目前为止,你应该让地图中心的特征放大5%边距。

答案 3 :(得分:37)

使用d3 v4,它变得更容易了!

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

享受,干杯

答案 4 :(得分:4)

您可以使用center()方法接受纬度/经度对。

据我所知,translate()仅用于字面移动地图的像素。我不知道如何确定比例是多少。

答案 5 :(得分: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);

如果您正在寻找精确缩放,这个答案将不适合您。但是,如果像我一样,你希望显示一个集中在容器中的地图,这应该就足够了。我试图显示墨卡托地图,发现这种方法对于集中我的地图非常有用,因为我不需要它,所以我很容易切断南极部分。

答案 6 :(得分:2)

除了Center a map in d3 given a geoJSON object之外,请注意,如果要在对象的边界周围指定填充,则可能更喜欢fitExtent()而不是fitSize()fitSize()会自动将此填充设置为0。

答案 7 :(得分:1)

要平移/缩放地图,您应该查看在传单上覆盖SVG。这比转换SVG要容易得多。请参阅此示例http://bost.ocks.org/mike/leaflet/,然后How to change the map center in leaflet

答案 8 :(得分:0)

根据mbostocks的回答和Herb Caudill的评论,我开始遇到阿拉斯加的问题,因为我使用的是墨卡托投影。我应该注意到,就我自己的目的而言,我正在努力为美国各国投资和关注。我发现我必须将这两个答案与Jan van der Laan的答案结合起来,以及与半球重叠的多边形的例外情况(多边形最终为东西方的绝对值大于1):

  1. 在mercator中设置一个简单的投影:

    projection = d3.geo.mercator()。scale(1).translate([0,0]);

  2. 创建路径:

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

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

    我希望这会有所帮助。

答案 9 :(得分:0)

对于想要调整verticaly et horizo​​ntaly的人来说,这是解决方案:

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

答案 10 :(得分: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)