缩放 d3 v4 地图以适合 SVG(或完全适合) [英] Scaling d3 v4 map to fit SVG (or at all)

查看:23
本文介绍了缩放 d3 v4 地图以适合 SVG(或完全适合)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to make this map of the us scale smaller. Either to my SVG, or even manually.

这是我最简单的代码:

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 错误.感谢您的帮助!

but every time I add anything to my path variable I get NAN errors from the internal parts of D3. Thanks for any help!

推荐答案

为什么数据投影不正确

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

Why the data doesn't project properly

The key issue is that your data is already projected. D3 geoProjections use data that is unprojected, or in lat long pairs. Data in the WGS84 datum. Essentially a d3 geoProjection takes spherical coordinates and translates them into planar cartesian x,y coordinates.

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

Your data does not conform to this - it is already planar. You can see most evidently because Alaska is not where it should be (unless someone changed the lat long pairs of Alaska, which is unlikely). Other signs and symptoms of already projected data may be a feature that covers the entire planet, and NaN errors.

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

That this is a composite projection makes it hard to unproject, but you can display already projected data in d3.js.

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

Most simply, you can define your projection as null:

var path = d3.geoPath(null);

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

This will take the x,y data from the geojson geometries and display it as x,y data. However, if your x,y coordinates exceed the width and height of your svg, the map will not be contained within your svg (as you found in your example with .attr("d", d3.geoPath());).

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

The particular file in this question is pre-projected to fit a 960x600 map, so this is ideal for a null projection - it was designed with the dimensions in mind. Its units are pixels and all coordinates fall within the desired dimensions. However, most projected geometries use coordinate systems with units such as meters, so that the bounding box of the feature's coordinates may be millions of units across. In these cases the null projection won't work - it'll convert a map unit value to a pixel value with no scaling.

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

With d3, A null projection is commonly used with geojson/topojson that is preprojected to fit a specified viewport using a d3 projection. See command line cartography for an example (the example uses unprojected source files - the same issues that arise from using a d3 projection on projected data apply in both browser and command line). The primary advantage of preprojecting a file for use with a null projection is performance.

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

If all you need is to scale and center the features, you can use a geoIdentity. This is implements a geoTransform but with standard projection methods such as scale, translate, and most importantly - fitSize/fitExtent. So, we can set the projection to a geoIdentity:

var projection = d3.geoIdentity();

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

This currently does the same as the null projection used above, it takes x,y data from the geojson geometries and displays it as x,y data with no transform - treating each coordinate in the geojson as a pixel coordinate. But, we can apply fitSize to this (or fitExtent) which will automatically scale and translate the data into the specified bounding box:

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轴:

Keep in mind, most projected data uses geographic conventions, y=0 is at the bottom, with y values increasing as one moves north. In svg/canvas coordinate space, y=0 is at the top, with y values increasing as one moves down. So, we will often need to flip the y axis:

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

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

This particular dataset: https://d3js.org/us-10m.v1.json was projected with a d3 projection, so its y axis has already been flipped as d3 projections project to a svg or canvas coordinate space.

geoIdentity 演示

var width = 600;
var height = 300;

var svg = d3.select("body").append("svg")
 .attr("width", width)
 .attr("height", height);



d3.json("https://d3js.org/us-10m.v1.json", function (error, us){
  var featureCollection = topojson.feature(us, us.objects.states);
  
  var projection = d3.geoIdentity()
  .fitExtent([[50,50],[600-50,300-50]], featureCollection)

  var path = d3.geoPath().projection(projection)
  
  svg.append("g")
    .attr("class", "states")
    .selectAll("path")
    .data(featureCollection.features)
    .enter().append("path")
    .attr("fill", "gray")
    .attr("d", path);
  
});

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/2.2.0/topojson.js"></script>

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

If you want a little more control over how that data is displayed you can use a geoTransform.

来自迈克·博斯托克:

但是如果您的几何图形已经是平面的呢?也就是说,如果你只是想要采用投影几何图形,但仍将其转换或缩放为适合视口吗?

But what if your geometry is already planar? That is, what if you just want to take projected geometry, but still translate or scale it to fit the viewport?

您可以实现自定义几何变换以获得完全控制整个投影过程.

You can implement a custom geometry transform to gain complete control over the projection process.

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

To use a geoTransform is relatively straightforward assuming that you do not want to change the type of projection. For example, if you want to scale the data you could implement a short function for scaling with 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));

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

Though, this will scale everything into the top left corner as you zoom out. To keep things centered, you could add some code to center the projection:

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 演示:

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

Here is an example using your file and a geoTransform:

var width = 600;
var height = 300;

var svg = d3.select("body").append("svg")
 .attr("width", width)
 .attr("height", height);


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);
    }
  });
}
  
d3.json("https://d3js.org/us-10m.v1.json", function (error, us){
  var path = d3.geoPath().projection(scale(0.2,width,height))
 
  svg.append("g")
    .attr("class", "states")
    .selectAll("path")
    .data(topojson.feature(us, us.objects.states).features)
    .enter().append("path")
    .attr("fill", "gray")
    .attr("d", path);
  
});

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/2.2.0/topojson.js"></script>

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

This method is useful under certain circumstances. But it requires you to know the projection that was used to create your data. Using QGIS/ArcGIS or even mapshaper you can change the data's projection so that it is "projected" as WGS84 (aka EPSG 4326). Once converted you have unprojected data.

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

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

If you don't know the projection used to create the data, you can't unproject it - you don't know what transformation was applied and with what parameters.

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

Once unprojected, you can use regular d3 projections as normal as you have coordinates in the correct coordinate space: longitude latitude pairs.

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

Unprojecting is useful if you also have unprojected data and want to mix both in the same map. Alternatively you could project the unprojected data so that both use the same coordinate system. Combining unmatched coordinate systems in a map with d3 is not easy and d3 is likely not the correct vehicle for this. If you really want to replicate a specific projection with d3 to match features that are already projected with unprojected features, then this question may be useful.

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

You could do check to see that the geometry of your features respect the limits of latitude and longitude. For example, if you were to log:

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.不太可能是长对.

You will quickly see that values are in excess of +/- 90 degrees N/S and +/- 180 degrees E/W. Unlikely to be lat long pairs.

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

Alternatively, you could import your data to an online service such as mapshaper.org and compare against another topojson/geojson that you know is unprojected (or 'projected' using WGS84).

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

If dealing with geojson, you may be lucky enough to see a property that defines the projection, such as: "name": "urn:ogc:def:crs:OGC:1.3:CRS84" (CRS stands for coordinate reference system) or an EPSG number: EPSG:4326 (EPSG stands for European Petroleum Survey Group).

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

Also, if your data projects with a null projection but not a standard projection (scaled/zoomed out to ensure you aren't looking in the wrong area), you might be dealing with projected data. Likewise if your viewport is entirely covered by one feature (and you aren't zoomed in). NaN coordinates are also a potential indicator. However, these last indicators of projected data can also mean other problems.

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

Lastly, the data source may also indicate data is already projected either in meta data or how it is used: Looking at this block, we can see that no projection was used when the geoPath is defined.

这篇关于缩放 d3 v4 地图以适合 SVG(或完全适合)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆