javascript - 为什么 D3 无法正确渲染 TopoJSON?

标签 javascript json d3.js topojson

我正在尝试使用 D3.jsTopoJSON 渲染瑞士 map 。底层JSON可以找到here 。首先我尝试关注this tutorial在我无法渲染任何看起来像 map 一样远程的东西之后,我发现 this question on here带有一个工作示例的链接。从我获取当前正在使用的代码的位置:

<!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 看起来没问题(在复制/粘贴后正确渲染的一些在线平台上进行了检查)并且只有很少的代码可能会出错,我认为错误出在投影参数中。摆弄了一下无法使其发挥作用。因此,我们将非常感谢任何帮助!

最佳答案

你是对的,错误在于投影。 但是,错误取决于您的数据是投影还是未投影(经纬度对)。

未投影的数据

如果您有 WGS84 格式的数据 - 即经纬度对,那么您会遇到以下问题:

使用您的投影,但仅更改数据源,我得到这样的结果(我剃掉了右侧的空海洋):

enter image description here

要正确居中墨卡托,您需要知道感兴趣区域的中心坐标。这通常是相当普遍的,对于瑞士我可能会尝试 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])

再次注意负旋转值。第二种选择需要更高的比例值,因为这种方法本质上将瑞士视为墨卡托投影上的零,零 - 并且沿着赤道的土地面积被最小化。

使用第二个预测,我得到: enter image description here

因此,您必须稍微调整一下比例,但现在您应该能够看到您的数据(假设您的数据是正确的经纬度对)。

预计数据

根据下面的评论(其中包含链接的 json 文件),我们可以看到这是您的问题。

对此有两种可能的解决方案:

  1. 将数据转换为经纬度对
  2. 使用地理变换

选项一是最简单的,您将取消数据投影 - 这需要知道当前的投影。在 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 坐标流并以指定的方式转换它们。

要平移 map 使其居中,您需要知道中心坐标。您可以使用 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 返回要素的左上角和右下角。这是关键部分,您可以制作自动缩放功能,那里有很多示例,但我喜欢经常手动缩放。如果 map 居中,这很容易。对于您的 map ,您的 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);
        }
    });
}

这里 cxcy 指的是特征的中间,宽度和高度指的是 svg 元素的宽度和高度 - 我们不需要特征聚集在 SVG 点 [0,0]。

总而言之,这给了我们类似的东西(比例因子为 0.002):

enter image description here

这是更新的 JSbin:http://jsbin.com/wolamuzeze/edit?html,output

请记住,比例取决于窗口大小,因为您的宽度/高度与您的情况下的窗口大小相关。最好通过自动设置缩放级别来解决此问题,但如果您有标签(例如),这可能会产生问题。

这个答案也可能有帮助:Scaling d3 v4 map to fit SVG (or at all)

关于javascript - 为什么 D3 无法正确渲染 TopoJSON?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42953285/

相关文章:

javascript - Sublime Text 3 中的 JS 与 Node.js 构建系统

java - 如何在 JAX-RS 中映射动态 JSON

json - 按 JSON 数据类型排序 postgres

php - Laravel,将JSON数组转换为Array,并且只从Array中获取一个对象

javascript - DC.js - 满足条件时触发弹性调整大小

javascript - 如何在 fullpage.js 幻灯片上居中 div?

javascript - 调用分页渲染组件

javascript - D3 反转色标图以获得幅度

D3.js 添加带标签的链接

javascript - 单击更改 url 而不重新加载