小编典典

缩放d3 v4映射以适合SVG(或完全适合)

javascript

我正在尝试缩小美国地图。要么是我的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错误。谢谢你的帮助!


阅读 479

收藏
2020-05-01

共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中(如在示例中使用找到的
.attr("d", d3.geoPath());)。

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

对于d3,geojson / topojson通常使用空投影,该投影使用d3投影进行预投影以适合指定的视口。有关示例(示例使用未投影的源文件在投影数据上使用d3投影所引起的相同问题在浏览器和命令行中均适用)。预投影文件以用于null投影的主要优点是性能。

地理身份

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

var projection = d3.geoIdentity();

当前,这与上面使用的null投影相同,它从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 /画布坐标空间中,y =
0位于顶部,y值随着向下移动而增加。因此,我们经常需要翻转y轴:

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

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

如果您想进一步控制数据的显示方式,可以使用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演示

这是使用您的文件和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)。转换后,您将拥有未投影的数据。

在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。不可能是长对。

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

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

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

2020-05-01