为什么 D3 不能正确呈现 TopoJSON?
Why does D3 not render TopoJSON correctly?
我正在尝试使用 D3.js
和 TopoJSON
渲染瑞士地图。底层 JSON 可以在 here. First I tried to follow this tutorial and after I couldn't render anything looking remotely like a map, I found 中找到,其中 link 是一个工作示例。从我获取此代码的位置我目前正在使用:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<style>
path {
fill: #ccc;
}
</style>
</head>
<body>
<h1>topojson simplified Switzerland</h1>
<script>
var width = window.innerWidth,
height = window.innerHeight;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.mercator()
.scale(500)
.translate([-900, 0]);
var path = d3.geo.path()
.projection(projection);
d3.json("ch-cantons.json", function(error, topology) {
if (error) throw error;
svg.selectAll("path")
.data(topojson.feature(topology, topology.objects.cantons).features).enter().append("path").attr("d", path);
});
</script>
</body>
</html>
因为 JSON
看起来没问题(在 copy/pasting 之后正确呈现它的一些在线平台上进行了检查)并且只有很少的代码可能出错,我假设错误在投影中参数。摆弄了一下无法使其工作。因此,我们将不胜感激任何帮助!
你是对的,错误在投影中。
但是,错误取决于您的数据是投影还是未投影(经纬度对)。
未投影的数据
如果您有 WGS84 格式的数据 - 也就是说经纬度对,那么您会遇到这个问题:
使用你的投影,但只改变数据源我得到这样的东西(我刮掉了右边的空海):
要使墨卡托正确居中,您需要知道感兴趣区域的中心坐标。这通常可以相当一般,对于瑞士我可能会尝试 47N 8.25E。
一旦你有了这个坐标,你需要把它放在中间。一种方法是在 x 轴上旋转并以 y 轴为中心:
var projection = d3.geo.mercator()
.scale(3500)
.rotate([-8.25,0])
.center([0,47])
.translate([width/2,height/2])
请注意,x 轴旋转是负的,您正在投影下方旋转地球。
另一种选择是在 x 和 y 轴上旋转,当您接近两极时,这可能是首选选项 - 因为墨卡托失真在高纬度地区变得不可行。这种方法看起来像:
var projection = d3.geo.mercator()
.scale(4000)
.rotate([-8.25,-47])
.center([0,0])
.translate([width/2,height/2])
再次注意负旋转值。第二个选项需要更高的比例值,因为这种方法基本上将瑞士视为零,在墨卡托投影中为零 - 并且赤道沿线的土地面积被最小化。
使用这些预测中的第二个,我得到:
因此您必须稍微调整比例,但现在您应该能够看到您的数据(假设您的数据是正确的经纬度对)。
预计数据
根据下面的评论,其中包括链接的 json 文件,我们可以看出这是您的问题。
对此有两种可能的解决方案:
- 将数据转换为经纬对
- 使用地理变换
选项一是最简单的,您将取消投影数据 - 这需要知道当前的投影。在 GIS 软件中,这通常会将其投影为 WGS84,这可以说不是真正的投影而是基准面。一旦你有了经纬度对,你就可以按照上面的步骤处理未投影的数据。
选项二完全跳过 d3.geoProjection。相反,我们将创建一个转换函数,将投影坐标转换为所需的 SVG 坐标。
地理投影看起来像:
function scale (scaleFactor) {
return d3.geo.transform({
point: function(x, y) {
this.stream.point(x * scaleFactor, (y * scaleFactor);
}
});
}
并且像投影一样使用:
var path = d3.geo.path().projection(scale(0.1));
它只是采用已经是笛卡尔坐标的 x,y 坐标流,并以指定的方式转换它们。
要平移地图使其居中,您需要知道中心坐标。您可以通过 path.bounds
:
找到它
var bounds = path.bounds(features);
var centerX = (bounds[0][0] + bounds[1][0])/2;
var centerY = (bounds[0][1] + bounds[1][1])/2;
Path.bounds returns 要素的左上角和右下角。这是关键部分,你可以做一个自动缩放功能,那里有很多例子,但我经常喜欢手动缩放。如果地图居中,这很容易。对于您的地图,geoTransform 可能如下所示:
function scale (scaleFactor,cx,cy,width,height) {
return d3.geo.transform({
point: function(x, y) {
this.stream.point((x-cx) * scaleFactor + width/2, (y-cy) * scaleFactor +height/2);
}
});
}
此处 cx
和 cy
指的是特征的中间,宽度和高度指的是 svg 元素的宽度和高度 - 我们不希望特征聚集在 SVG 点[0,0].
总而言之,这给了我们类似的东西(比例因子为 0.002):
这是更新后的 JSbin:http://jsbin.com/wolamuzeze/edit?html,output
请记住,比例取决于 window 尺寸,因为您的 width/height 是相对于 window 尺寸的。这可能最好通过自动设置缩放级别来解决,但如果您有标签(例如),这可能会产生问题。
这个答案也可能有帮助:
我正在尝试使用 D3.js
和 TopoJSON
渲染瑞士地图。底层 JSON 可以在 here. First I tried to follow this tutorial and after I couldn't render anything looking remotely like a map, I found
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<style>
path {
fill: #ccc;
}
</style>
</head>
<body>
<h1>topojson simplified Switzerland</h1>
<script>
var width = window.innerWidth,
height = window.innerHeight;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.mercator()
.scale(500)
.translate([-900, 0]);
var path = d3.geo.path()
.projection(projection);
d3.json("ch-cantons.json", function(error, topology) {
if (error) throw error;
svg.selectAll("path")
.data(topojson.feature(topology, topology.objects.cantons).features).enter().append("path").attr("d", path);
});
</script>
</body>
</html>
因为 JSON
看起来没问题(在 copy/pasting 之后正确呈现它的一些在线平台上进行了检查)并且只有很少的代码可能出错,我假设错误在投影中参数。摆弄了一下无法使其工作。因此,我们将不胜感激任何帮助!
你是对的,错误在投影中。 但是,错误取决于您的数据是投影还是未投影(经纬度对)。
未投影的数据
如果您有 WGS84 格式的数据 - 也就是说经纬度对,那么您会遇到这个问题:
使用你的投影,但只改变数据源我得到这样的东西(我刮掉了右边的空海):
要使墨卡托正确居中,您需要知道感兴趣区域的中心坐标。这通常可以相当一般,对于瑞士我可能会尝试 47N 8.25E。
一旦你有了这个坐标,你需要把它放在中间。一种方法是在 x 轴上旋转并以 y 轴为中心:
var projection = d3.geo.mercator()
.scale(3500)
.rotate([-8.25,0])
.center([0,47])
.translate([width/2,height/2])
请注意,x 轴旋转是负的,您正在投影下方旋转地球。
另一种选择是在 x 和 y 轴上旋转,当您接近两极时,这可能是首选选项 - 因为墨卡托失真在高纬度地区变得不可行。这种方法看起来像:
var projection = d3.geo.mercator()
.scale(4000)
.rotate([-8.25,-47])
.center([0,0])
.translate([width/2,height/2])
再次注意负旋转值。第二个选项需要更高的比例值,因为这种方法基本上将瑞士视为零,在墨卡托投影中为零 - 并且赤道沿线的土地面积被最小化。
使用这些预测中的第二个,我得到:
因此您必须稍微调整比例,但现在您应该能够看到您的数据(假设您的数据是正确的经纬度对)。
预计数据
根据下面的评论,其中包括链接的 json 文件,我们可以看出这是您的问题。
对此有两种可能的解决方案:
- 将数据转换为经纬对
- 使用地理变换
选项一是最简单的,您将取消投影数据 - 这需要知道当前的投影。在 GIS 软件中,这通常会将其投影为 WGS84,这可以说不是真正的投影而是基准面。一旦你有了经纬度对,你就可以按照上面的步骤处理未投影的数据。
选项二完全跳过 d3.geoProjection。相反,我们将创建一个转换函数,将投影坐标转换为所需的 SVG 坐标。
地理投影看起来像:
function scale (scaleFactor) {
return d3.geo.transform({
point: function(x, y) {
this.stream.point(x * scaleFactor, (y * scaleFactor);
}
});
}
并且像投影一样使用:
var path = d3.geo.path().projection(scale(0.1));
它只是采用已经是笛卡尔坐标的 x,y 坐标流,并以指定的方式转换它们。
要平移地图使其居中,您需要知道中心坐标。您可以通过 path.bounds
:
var bounds = path.bounds(features);
var centerX = (bounds[0][0] + bounds[1][0])/2;
var centerY = (bounds[0][1] + bounds[1][1])/2;
Path.bounds returns 要素的左上角和右下角。这是关键部分,你可以做一个自动缩放功能,那里有很多例子,但我经常喜欢手动缩放。如果地图居中,这很容易。对于您的地图,geoTransform 可能如下所示:
function scale (scaleFactor,cx,cy,width,height) {
return d3.geo.transform({
point: function(x, y) {
this.stream.point((x-cx) * scaleFactor + width/2, (y-cy) * scaleFactor +height/2);
}
});
}
此处 cx
和 cy
指的是特征的中间,宽度和高度指的是 svg 元素的宽度和高度 - 我们不希望特征聚集在 SVG 点[0,0].
总而言之,这给了我们类似的东西(比例因子为 0.002):
这是更新后的 JSbin:http://jsbin.com/wolamuzeze/edit?html,output
请记住,比例取决于 window 尺寸,因为您的 width/height 是相对于 window 尺寸的。这可能最好通过自动设置缩放级别来解决,但如果您有标签(例如),这可能会产生问题。
这个答案也可能有帮助: