【问题标题】:Why does D3 not render TopoJSON correctly?为什么 D3 不能正确渲染 TopoJSON?
【发布时间】:2017-03-22 13:37:37
【问题描述】:

我正在尝试使用D3.jsTopoJSON 渲染瑞士地图。底层 JSON 可以在 here 找到。首先,我尝试关注this tutorial,在无法渲染任何看起来像地图的远程内容后,我发现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 看起来不错(在复制/粘贴后正确呈现它的一些在线平台上进行了检查)并且只有很少的代码可能出错,我认为错误出在投影参数中。稍微折腾一下就不行了。因此,我们将不胜感激任何帮助!

【问题讨论】:

    标签: javascript json d3.js topojson


    【解决方案1】:

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

    未投影数据

    如果您有 WGS84 中的数据 - 即 lat long 对,那么您有这个问题:

    使用你的投影,但只改变数据源我得到了这样的东西(我把右边的空海刮掉了):

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

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

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

    选项一是最简单的,您将取消投影数据 - 这需要知道当前的投影。在 GIS 软件中,这通常会将其投影为 WGS84,可以说这不是真正的投影,而是基准。获得 lat long 对后,您可以按照上述步骤获取未投影的数据。

    选项二完全跳过 d3.geoProjection。相反,我们将创建一个转换函数,将投影坐标转换​​为所需的 SVG 坐标。

    地理投影如下所示:

    function scale (scaleFactor) {
        return d3.geo.transform({
            point: function(x, y) {
                this.stream.point(x * scaleFactor, (y * scaleFactor);
            }
        });
    }
    

    And 用作投影:

    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 返回特征的左上角和右下角。这是关键部分,你可以做一个自动缩放功能,那里有很多例子,但我喜欢经常手动缩放。如果地图居中,这很容易。对于您的地图,您的 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):

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

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

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

    【讨论】:

    • 感谢您的帮助。我尝试按照建议更改投影,但只会弄得一团糟。这是一个jsbin,上面说是一团糟。
    • 啊,这是一个不同的问题。您根本不需要投影 - 您的数据已经被投影。如果将投影设置为空,您将看到坐标不是经纬度对 - d3 geoPaths 采用经纬度对表示球形地球上的位置,并将它们投影到二维平原上。解决此问题的最简单方法是(取消)投影您的数据,以便它使用经纬度对(使用基准 WGS84)。我将在今晚晚些时候使用 geoTransform 编辑答案以正确居中此文件。
    • 非常感谢。稍后将对其进行更彻底的研究。但现在它至少看起来像一张地图。
    猜你喜欢
    • 1970-01-01
    • 2015-05-14
    • 2021-08-06
    • 2018-03-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-11
    • 2022-06-15
    • 2021-05-28
    相关资源
    最近更新 更多