给定一个 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)和. trans ([0,-1200]) ?

69452 次浏览

您可以使用一个 中心()方法来接受 lat/lon 对。

据我所知,trans ()只用于逐字地移动地图的像素。我不知道如何确定什么是规模。

要平移/缩放地图,您应该查看传单上 SVG 的覆盖情况。这比转换 SVG 要容易得多。看这个例子 http://bost.ocks.org/mike/leaflet/,然后是 如何更改传单中的地图中心

下面的内容似乎与您想要的内容大致相同。比例看起来没问题。当应用它到我的地图有一个小的偏移。这个小偏移量可能是因为我使用了 trans 命令来使映射居中,而我可能应该使用 center 命令。

  1. 创建一个投影和 d3.ge.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);

在单位投影中计算特征的 包围盒之后,您可以通过比较边界框(b[1][0] - b[0][0]b[1][1] - b[0][1])的长宽比和画布(widthheight)的长宽比来计算合适的 规模。在这个示例中,我还将边框的比例扩展到了画布的95% ,而不是100% ,因此在边缘有一点额外的空间用于笔触和周围的特性或填充。

然后可以使用包围盒的中心((b[1][0] + b[0][0]) / 2(b[1][1] + b[0][1]) / 2)和画布的中心(width / 2height / 2)计算 翻译。注意,由于边界框位于单位投影的坐标中,它必须乘以比例(s)。

例如,Bl.ocks.org/4707858:

project to bounding box

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

例如,Bl.ocks.org/4699541:

zoom to bounding box

我在互联网上寻找一种不引人注目的方式来集中我的地图,并受到了 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);

如果你想要精确的比例,这个答案对你不起作用。但是,如果您像我一样希望显示一个集中在容器中的映射,那么这就足够了。我试图显示墨卡托地图,发现这种方法对于集中我的地图很有用,而且我可以很容易地切断南极部分,因为我并不需要它。

我是 d3的新手,我会试着解释我是如何理解它的,但是我不确定我做的一切都是对的。

秘诀在于知道一些方法将在制图空间(纬度,经度)上操作,而另一些方法将在笛卡尔空间(屏幕上的 x,y)上操作。制图空间(我们的星球)是(几乎)球形的,笛卡尔空间(屏幕)是平的——为了把一个映射到另一个上面,你需要一个算法,叫做 投影。这个空间太短,无法深入探究投影这个迷人的主题,以及它们如何扭曲地理特征,以便将球面变成平面; 有些是为了保持角度,有些是为了保持距离等等——总会有一个折衷方案(Mike Bostock 有一个 大量的例子)。

enter image description here

在 d3中,投影对象有一个中心属性/setter,以映射单位表示:

中心([位置])

如果指定了居中位置,则将投影的居中位置设置为指定的位置,即一个以度为单位的两元素数组,然后返回投影经纬度。如果没有指定中心,则返回默认为 something 0 ° ,0 ° 的当前中心。

还有以像素表示的平移——投影中心相对于画布的位置:

翻译([点])

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

当我想在画布中心放置一个特征时,我喜欢将投影中心设置为特征边界框的中心——当我使用 墨卡托(WGS 84,在谷歌地图中使用)为我的国家(巴西) ,从未使用其他投影和半球进行测试时,这对我很有用。你可能不得不为其他情况做出调整,但如果你能坚持这些基本原则,你就会做得很好。

例如,给定一个投影和路径:

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


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

来自 pathbounds方法返回边界框 以像素为单位。使用它来找到正确的比例,比较像素的大小和地图单位的大小(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);

enter image description here

使用 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% 的边缘。

随着 mbostock 的回答和 Herb Caudill 的评论,我开始遇到阿拉斯加的问题,因为我使用的是麦卡托投影法。我应该注意到,出于我自己的目的,我正试图投射和中心美国国家。我发现我必须将两个答案与 Jan van der Laan 的答案结合起来,但是对于重叠在半球上的多边形(多边形最终的绝对值大于1)有以下例外:

  1. 在墨卡托建立一个简单的投影:

    投影 = d3.ge.mercator () . 比例尺(1) . 翻译([0,0]) ;

  2. 创造道路:

    Path = d3.ge.path () . 投影(投影) ;

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

希望这个能帮上忙。

对于那些想要纵向和横向调整的人来说,这里有一个解决方案:

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

我是如何将一个 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)

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

好好享受,干杯

除了 给定一个 GeoJSON 对象,在 d3中将一个映射居中之外,如果希望在对象的边界周围指定填充,请注意您可能更喜欢使用 fitExtent()而不是 fitSize()fitSize()自动将此填充设置为0。