缩放 d3 v4 地图以适合 SVG(或完全适合)

IT技术 javascript d3.js svg geojson topojson
2021-01-18 10:46:41

我正在尝试缩小这张美国地图的比例。要么是我的 SVG,要么是手动。

这是我最简单的代码:

function initializeMapDifferent(){
    var svg = d3.select("#map").append("svg")
        .attr("width", 1000)
        .attr("height", 500);



    d3.json("https://d3js.org/us-10m.v1.json", function (error, us){

        svg.append("g")
            .attr("class", "states")
            .selectAll("path")
            .data(topojson.feature(us, us.objects.states).features)
            .enter().append("path")
            .attr("fill", "gray")
            .attr("d", d3.geoPath());
    });
}

我尝试过类似的事情:

  var path = d3.geoPath()
  .projection(d3.geoConicConformal()
      .parallels([33, 45])
      .rotate([96, -39])
      .fitSize([width, height], conus));

但是每次我向路径变量添加任何内容时,我都会从 D3 的内部部分收到 NAN 错误。谢谢你的帮助!

1个回答

为什么数据不能正确投影

关键问题是您的数据已经被预测。D3 geoProjections 使用未投影的数据或成对的经纬度数据。WGS84 数据中的数据。本质上,d3 geoProjection 采用球面坐标并将它们转换为平面笛卡尔 x,y 坐标。

您的数据不符合这一点 - 它已经是平面的。你可以最明显地看到,因为阿拉斯加不是它应该在的地方(除非有人改变了阿拉斯加的纬度,这不太可能)。已预测数据的其他迹象和症状可能是覆盖整个星球的特征和 NaN 错误。

这是一个复合投影,因此很难取消投影,但您可以在 d3.js 中显示已经投影的数据。

“投影”已经投影的数据

零投影:

最简单的是,您可以将投影定义为 null:

var path = d3.geoPath(null);

这将从 geojson 几何中获取 x,y 数据并将其显示为 x,y 数据。但是,如果您的 x,y 坐标超过 svg 的宽度和高度,则地图将不会包含在您的 svg 中(如您在示例中找到的 svg .attr("d", d3.geoPath());)。

此问题中的特定文件已预先投影以适合 960x600 地图,因此这非常适合零投影 - 它的设计考虑了尺寸。它的单位是像素,所有坐标都在所需的维度内。但是,大多数投影几何图形使用以米为单位的坐标系,因此要素坐标的边界框可能跨越数百万个单位。在这些情况下,空投影将不起作用 - 它会将地图单位值转换为没有缩放的像素值。

对于 d3,空投影通常与 geojson/topojson 一起使用,它使用 d3 投影预先投影以适应指定的视口。有关示例,请参见命令行制图(该示例使用未投影的源文件 - 对投影数据使用 d3 投影所产生的问题同样适用于浏览器和命令行)。预投影文件以用于空投影的主要优点是性能

地理标识

如果您只需要缩放和居中要素,则可以使用 geoIdentity。这是实现 geoTransform 但使用标准投影方法,例如scale, translate,最重要的是 - fitSize/ fitExtent因此,我们可以将投影设置为 geoIdentity:

var projection = d3.geoIdentity();

这目前与上面使用的空投影相同,它从 geojson 几何中获取 x,y 数据并将其显示为 x,y 数据,没有变换 - 将 geojson 中的每个坐标视为像素坐标。但是,我们可以将 fitSize 应用于此(或 fitExtent),它将自动缩放数据并将其转换为指定的边界框:

var projection = d3.geoIdentity()
  .fitSize([width,height],geojsonObject);

或者

var projection = d3.geoIdentity()
  .fitExtent([[left,top],[right,bottom]], geojsonObject);

请记住,大多数投影数据使用地理约定,y=0 位于底部,y 值随着向北移动而增加。在 svg/canvas 坐标空间中,y=0 位于顶部,y 值随着向下移动而增加。所以,我们经常需要翻转 y 轴:

var projection = d3.geoIdentity()
 .fitExtent([width,height],geojsonObject)
 .reflectY(true);

这个特定的数据集:https : //d3js.org/us-10m.v1.json是用 d3 投影投影的,所以它的 y 轴已经作为 d3 投影项目翻转到 svg 或画布坐标空间。

地理身份演示

地理变换

如果您想更多地控制数据的显示方式,可以使用geoTransform.

来自迈克博斯托克

但是,如果您的几何图形已经是平面的呢?也就是说,如果您只想获取投影几何图形,但仍要平移或缩放它以适应视口,该怎么办?

您可以实施自定义几何变换以完全控制投影过程。

geoTransform假设您不想更改投影类型,使用 a相对简单例如,如果您想缩放数据,您可以实现一个用于缩放的简短函数geoTransform

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

var path = d3.geoPath().projection(scale(0.2));

但是,当您缩小时,这会将所有内容缩放到左上角。为了使事情居中,您可以添加一些代码来使投影居中:

function scale (scaleFactor,width,height) {
    return d3.geoTransform({
        point: function(x, y) {
            this.stream.point( (x - width/2) * scaleFactor + width/2 , (y - height/2) * scaleFactor + height/2);
        }
    });
    }

var path = d3.geoPath().projection(scale(0.2,width,height))

地理变换演示

这是使用您的文件和 geoTransform 的示例:

取消投影数据

这种方法在某些情况下很有用。但它要求您知道用于创建数据的投影。使用 QGIS/ArcGIS 甚至 mapshaper,您可以更改数据的投影,以便将其“投影”为 WGS84(又名 EPSG 4326)。转换后,您将获得未投影的数据。

在 Mapshaper 中,使用 shapefile 非常容易,只需将 shapefile 的 .dbf、.shp 和 .prj 文件拖入窗口即可。在 mapshaper 中打开控制台并输入 proj wgs84。

如果您不知道用于创建数据的投影,则无法取消投影 - 您不知道应用了哪些转换以及使用了哪些参数。

未投影后,您可以正常使用常规 d3 投影,因为您在正确的坐标空间中拥有坐标:经度纬度对。

如果您还有未投影的数据并希望将两者混合在同一地图中,则取消投影很有用。或者,您可以投影未投影的数据,以便两者使用相同的坐标系。将地图中不匹配的坐标系与 d3 结合起来并不容易,而且 d3 可能不是正确的工具。如果你真的想用 d3 复制一个特定的投影来匹配已经用未投影的特征投影的特征,那么这个问题可能很有用。

你怎么知道你的数据是否已经被投影了?

您可以检查一下要素的几何形状是否符合纬度和经度的限制。例如,如果您要记录:

d3.json("https://d3js.org/us-10m.v1.json", function (error, us){
   console.log(topojson.feature(us, us.objects.states).features);
});

您会很快看到值超过 +/- 90 度 N/S 和 +/- 180 度 E/W。不太可能是lat long对。

或者,您可以将您的数据导入到诸如 mapshaper.org 之类的在线服务,并与您知道未投影(或使用 WGS84“投影”)的另一个 topojson/geojson 进行比较。

如果处理geojson,您可能很幸运地看到定义投影的属性,例如:"name": "urn:ogc:def:crs:OGC:1.3:CRS84"(CRS 代表坐标参考系统)或EPSG 编号:EPSG:4326(EPSG 代表欧洲石油调查组)。

此外,如果您的数据使用空投影而不是标准投影(缩放/缩小以确保您没有查看错误区域)进行投影,则您可能正在处理投影数据。同样,如果您的视口完全被一个特征覆盖(并且您没有放大)。NaN 坐标也是一个潜在的指标。然而,这些预测数据的最后指标也可能意味着其他问题。

最后,数据源也可能表明数据已经在元数据中或如何使用它:查看这个,我们可以看到在geoPath定义时没有使用任何投影

非常感谢您的帮助。我将解决细节问题,但这提供了一个解决方案,我非常感谢!
2021-03-23 10:46:41
我很高兴,如果您确实有美国的未投影 geojson 或 topojson,但您不想单独关闭阿拉斯加和夏威夷,您可以使用 geoAlbersUsa 投影,它是一种复合投影,基本上重新创建了文件的布局在这个问题中。信息在:github.com/d3/d3-geo/blob/master/README.md#geoAlbersUsa
2021-04-06 10:46:41