首页 > 解决方案 > d3.js v5:无法获取要在美国地图上显示的数据点

问题描述

我可以成功获得要渲染的美国地图,但是我的数据点没有。(我了解 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

标签: d3.jsgeojsontopojson

解决方案


这里有很多问题:

传递 .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 是[longitude,latitude], or [x,y](您在这里获取 y 坐标,但设置 x 值)。

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

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

您正在获取经度并直接将其转换为像素值。但是您的 geojson 使用 3D 坐标空间(3D 地球上的未投影点),单位为度数。如果我们简单地将其转换为像素,cx = -113您的圆圈将出现在 SVG 左侧的屏幕外。

使用投影

您需要投影您的数据,为此我们将定义一个投影函数并使用类似的东西:

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

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

投影采用 3 维空间中的未投影坐标(地球上的点或经纬度对),以度为单位,并返回 2 维空间中的点,以像素为单位。

但是,这现在把我们带到了最困难的部分:

你应该使用什么投影?

我们需要将我们的点与已经绘制的美国特征对齐,但是您没有为美国各州定义投影 - 如果您不向 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

我们可以用同样的方式绘制路径,只要我们找到一个未投影的美国 topojson/geojson,我们可以用以下方式放置点:

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

至于寻找未投影的美国 topojson,这里有一个。

这是一个使用上述方法并投影所有数据的工作版本(我还过滤特征以去除没有坐标的特征,美国阿尔伯斯可能会打破的特征,因为它是复合投影)。


推荐阅读