在给定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命令将地图居中,而我应该使用中心命令。
- 创build一个投影和d3.geo.path
- 计算当前投影的边界
- 使用这些界限来计算比例和翻译
- 重新创build投影
在代码中:
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的答案,但你可以稍微简化一下,因为你不需要计算地理质心; 你只需要边界框。 而且,通过使用未缩放,未翻译的单位投影,可以简化math运算。
代码的重要部分是这样的:
// 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]
的宽高比,计算单位投影中的要素边界框后, b[1][1] - b[0][1]
)与canvas的宽高比( width
和height
)。 在这种情况下,我还将边界框缩放到canvas的95%,而不是100%,所以在边缘上有一些额外的空间用于描边和周围的特征或填充。
(b[1][0] + b[0][0]) / 2
和(b[1][1] + b[0][1]) / 2
)和canvas的中心( width / 2
和height / 2
)。 请注意,由于边界框在单位投影的坐标中,因此必须乘以比例( s
)。
例如, bl.ocks.org/4707858 :
还有一个相关的问题是如何在不调整投影的情况下放大集合中的特定要素,即将投影与几何变换相结合以放大和缩小。 它使用与上面相同的原理,但是math稍有不同,因为几何变换(SVG“变换”属性)与地理投影相结合。
例如, bl.ocks.org/4699541 :
我是新来的d3 – 会试图解释我是如何理解的,但我不确定我是否正确。
秘密是知道一些方法将在笛卡尔空间(x,y在屏幕上)的制图空间(纬度,经度)和其他方面进行操作。 制图空间(我们的行星)是(几乎)球形的,笛卡尔空间(屏幕)是平坦的 – 为了映射一个在另一个之上,你需要一个algorithm,这就是所谓的投影 。 这个空间太短,不能深入到投影的迷人主题,以及它们如何扭曲地理特征,以便将球形转化为平面; 有些是为保存angular度而devise的,有些则是为了节省距离等等 – 总是有一个妥协scheme(Mike Bostock有大量的例子 )。
在d3中,投影对象具有中心属性/设置器,以地图单位给出:
projection.center([位置])
如果指定了中心,则将投影的中心设置为指定的位置,经度和纬度的双元素数组(以度为单位)并返回投影。 如果未指定中心,则返回当前中心,默认为⟨0°,0°</s>。
还有以像素为单位的翻译 – 投影中心相对于canvas的位置:
projection.translate([点])
如果指定了点,则将投影的平移偏移设置为指定的双元素数组[x,y]并返回投影。 如果未指定点,则返回默认为[480,250]的当前转换偏移量。 平移偏移确定了投影中心的像素坐标。 默认的平移偏移量位于960×500区域的中心,⟨0°,0°</s>。
当我想在canvas中居中放置一个特征时,我喜欢将投影中心设置为特征边界框的中心 – 这对于我的国家(巴西)使用mercator (WGS 84,用于谷歌地图)时适用,从来没有使用其他投影和半球进行testing。 您可能需要对其他情况进行调整,但是如果您确定了这些基本原则,您将会很好。
例如,给出一个投影和path:
var projection = d3.geo.mercator() .scale(1); var path = d3.geo.path() .projection(projection);
来自path
的bounds
方法以像素为单位返回边界框。 使用它来find正确的比例尺,以像素为单位的大小与地图单位中的大小进行比较(0.95给出了宽度或高度最佳拟合的5%的边距)。 基本几何在这里,计算矩形宽度/高度给定的对angular对angular:
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
方法将地图的中心移动到canvas的中心:
projection.translate([width/2, height/2]);
现在,您应该将地图中心的特征放大5%。
随着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");
享受,欢呼
有一个中心()方法,你可以使用,接受经纬度对。
据我所知,translate()仅用于从字面上移动地图的像素。 我不知道如何确定什么规模。
我在互联网上四处张望,以一种无忧无虑的方式来集中我的地图,并得到了Jan van der Laan和mbostock的答案的启发。 如果您使用svg的容器,使用jQuery的一个更简单的方法。 我为填充/边框等创build了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);
如果你正在寻找确切的缩放比例,这个答案不适合你。 但是如果像我一样,你希望显示集中在一个容器中的地图,这应该是足够的。 我试图展示墨卡托地图,发现这种方法在集中我的地图时非常有用,因为我不需要它,所以我可以很容易地切断南极的部分。
要平移/缩放地图,您应该查看覆盖Leaflet上的SVG。 这将比转换SVG容易得多。 看到这个例子http://bost.ocks.org/mike/leaflet/ ,然后如何更改传单中的地图中心
有了mbostocks的回答,还有Herb Caudill的评论,自从我使用墨卡托投影以来,我开始遇到与阿拉斯加有关的问题。 我应该注意到,为了我自己的目的,我正在试图把美国的中心投射到中心。 我发现我必须和Jan van der Laan的答案结婚,除了与半球重叠的多边形(东西方绝对值大于1的多边形)之外,还有一个例外:
-
在mercator中build立一个简单的投影:
(1).translate([0,0]);
-
创buildpath:
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); }
我希望这有帮助。
对于想要垂直和水平调整的人来说,解决方法是:
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,在那里我需要拉出的function:
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)