d3.js v5:无法在美国地图上显示数据点

3

我能成功地呈现美国地图,但我的数据点却没有显示。请注意,d3.js在v5中进行了一些重大更改,因此以前类似的问题不适用。

$(document).ready(function () {

var us = d3.json('https://unpkg.com/us-atlas@1/us/10m.json');
var meteoriteData = d3.json('https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/meteorite-strike-data.json');

var svg = d3.select("svg")
    .style("width", "1110px")
    .style("height", "714px");

var path = d3.geoPath();

Promise.all([us, meteoriteData]).then(function (values) {

    var map = values[0];
    console.log("map", map);

    var meteoriteData = values[1];
    console.log("meteoriteData", meteoriteData);

    svg.append("g")
        .attr("fill", "#ccc")
        .selectAll("path")
        .data(topojson.feature(map, map.objects.states).features)
        .enter().append("path")
        .attr("d", path),

        svg.append("path")
            .datum(topojson.mesh(map, map.objects.states, (a, b) => a !== b))
            .attr("fill", "none")
            .attr("stroke", "white")
            .attr("stroke-linejoin", "round")
            .attr("pointer-events", "none")
            .attr("d", path),

        svg.selectAll("circle")
            .data(meteoriteData)
            .enter()
            .append("circle")
            .attr("class", "circles")
            .attr("cx", function (d) { return ([d.geometry.coordinates[0], d.geometry.coordinates[1]])[1]; })
            .attr("cy", function (d) { return ([d.geometry.coordinates[0], d.geometry.coordinates[1]])[0]; })
            .attr("r", "1px");
});

});

这里可以找到一个可用的副本... https://codepen.io/lady-ace/pen/PooORoy

1个回答

2

这里有几个问题:

将数组传递给.data

首先,在使用时

svg.selectAll("circle")
   .data(meteoriteData)

selectAll().data()需要您传递一个函数或数组。在您的情况下,您需要将数据数组传递给它 - 但是meteoriteData是一个对象。它是一个具有以下结构的geojson要素集合:

{
  "type": "FeatureCollection",
  "features": [
      /* features */
  ]
}

所有单独的geojson功能都在该对象内的数组中。要获取特征数组,在这种情况下表示陨石的特征,我们需要使用:
svg.selectAll("circle")
   .data(meteoriteData.features)

现在我们可以为要素集合中的每个要素创建一个圆。如果这样做,您可以在检查SVG元素时找到这些圆,但它们不会被正确地放置。

定位点

如果您进行了上述更改,则不会在正确的位置看到圆圈。您在此处没有将圆圈定位正确:

 .attr("cx", function (d) { return ([d.geometry.coordinates[0], d.geometry.coordinates[1]])[1]; })

这与以下内容相同:

 .attr("cx", function(d) { return d.geometry.coordinates[1]; })

这里有两个问题:其一,geojson 是 [经度,纬度] 或者 [x,y](你在获取 y 坐标,但是设置了 x 值)。

但是,更大的问题是你没有对数据进行投影。这是来自你的 geojson 的原始坐标:

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [
      -113,
      54.21667
    ]
   ...

你正在将经度直接转换为像素值。但是,你的geojson使用一个三维坐标空间(未投影的三维地球上的点),其单位以度为度量。如果我们简单地将其转换为像素,cx = -113,那么你的圆将出现在SVG的左侧屏幕之外。

使用投影

你需要对数据进行投影处理,为此我们需要定义一个投影函数并使用类似以下的方式:

  .attr("cx", function(d) { return projection(d.geometry.coordinates)[0] })

此代码获取经度和纬度并将它们传递给投影函数,然后获取返回的x值,并将其设置为cx的值。

投影函数将具有以度为单位的三维空间中未投影坐标(在地球或长/纬对上的点)转换为以像素为单位的二维空间中的点。

但是,这现在带来了最困难的部分:

你应该使用哪种投影?

我们需要将我们的点与已经绘制的美国特征对齐,但是您没有为美国各州定义投影 - 如果您没有向d3.geoPath提供投影,则它使用空投影,将提供的坐标绘制在SVG上,就好像它们是像素坐标一样。不会发生任何变换。我知道您的美国特征已经被投影了,因为阿拉斯加不在它应该在的位置上,topojson中的坐标值超过了经度+/-180度,纬度+/-90度,而且地图看起来像是使用Albers投影进行投影。

如果geoPath没有使用d3投影对数据进行投影,但是数据似乎已经被投影,则数据是预先投影的 - 此topojson存储了已经投影的点。

我们有投影数据(美国各州)和未投影数据(陨石撞击),将它们混合在一起始终是个挑战。
挑战在于创建一个可以复制用于创建美国各州数据集的投影函数的投影。我们可以复制该投影,因为它是一个引发许多问题的问题文件。如何操作可以在此处找到解释。但这比应该的更加复杂:混合投影和未投影的数据是繁琐、不灵活且比必要的更加复杂。
我建议您对美国和陨石都使用未投影数据:
var projection = d3.geoAlbersUsa();             // create an Albers USA projection
var path = d3.geoPath().projection(projection); // set the projection for the path

只要我们找到一个未投影的美国拓扑JSON /地理JSON,我们就可以以相同的方式绘制路径,并使用以下方法放置点:

.attr("cx", function(d) { return projection(d.geometry.coordinates)[0]; })
.attr("cy", function(d) { return projection(d.geometry.coordinates)[1]; })

关于寻找未投影的美国拓扑JSON,这里有一个。

这里是使用上述方法并投影所有数据的可用版本(我还过滤了没有坐标、可能会在美国阿尔伯斯投影中出错的要素,因为它是一个组合投影)。


感谢你的详尽解答,Andrew-Reid。使用陨石数据,它运行得很好。然而,当我用从本地数据库获取的具有相同格式的数据集替换陨石数据时,它仍然无法工作。数据被返回了,但在地图上没有显示出来。我怀疑这可能是因为DOM在数据返回之前就已经渲染了。在Promise.all功能中是否有办法控制这个呢? - Jess Brawner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接