目前在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])?
答案 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%,因此在笔迹和周围特征或填充的边缘上有一点额外的空间。
然后,您可以使用边框(width
和height
)的中心和画布的中心((b[1][0] + b[0][0]) / 2
和{来计算翻译 {1}})。请注意,由于边界框位于单位投影的坐标中,因此必须乘以比例((b[1][1] + b[0][1]) / 2
)。
有一个相关的问题,即如何在不调整投影的情况下缩放到集合中的特定特征,即,将投影与几何变换相结合以放大和缩小。它使用与上述相同的原理,但数学略有不同,因为几何变换(SVG“变换”属性)与地理投影相结合。
答案 1 :(得分:123)
以下似乎大概做了你想要的。缩放似乎没问题。将它应用到我的地图时会有一个小的偏移量。这个小偏移可能是因为我使用translate命令使地图居中,而我应该使用center命令。
在代码中:
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)。
在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
使用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):
在mercator中设置一个简单的投影:
projection = d3.geo.mercator()。scale(1).translate([0,0]);
创建路径:
path = 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);
}
我希望这会有所帮助。
答案 9 :(得分:0)
对于想要调整verticaly et horizontaly的人来说,这是解决方案:
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)