javascript - 缩放 d3 v4 map 以适合 SVG(或完全适合)

标签 javascript d3.js svg geojson topojson

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

最佳答案

为什么数据不能正确投影

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

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

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

“投影”已经投影的数据

零投影:

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

var path = d3.geoPath(null);

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

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

对于 d3,空投影通常与 geojson/topojson 一起使用,它使用 d3 投影预先投影以适应指定的视口(viewport)。见 command line cartography例如(该示例使用未投影的源文件 - 对投影数据使用 d3 投影所产生的相同问题适用于浏览器和命令行)。预投影文件以用于空投影的主要优点是 performance .

地理标识

如果您只需要缩放和居中要素,则可以使用 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 投影投影的,因此当 d3 投影投影到 svg 或 Canvas 坐标空间时,它的 y 轴已经翻转。

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 .

来自 Mike Bostock :

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 实现一个用于缩放的简短函数。 :
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));

但是,当您缩小时,这会将所有内容缩放到左上 Angular 。为了使事情居中,您可以添加一些代码来使投影居中:
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 投影,因为您在正确的坐标空间中拥有坐标:经度纬度对。

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

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

您可以检查一下要素的几何形状是否符合纬度和经度的限制。例如,如果您要记录:
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 代表欧洲石油调查组)。

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

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

关于javascript - 缩放 d3 v4 map 以适合 SVG(或完全适合),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42430361/

相关文章:

javascript - d3.js - 图像 `mouseenter` stopPropagation 抛出错误

python - 从 Flask View 创建一个 JSON 文件以与 D3 一起使用

javascript - 对象标签加载 SVG 但其内容文档为空

css - 是否可以使用 CSS 缩放内联 SVG?

javascript - 使用按钮关闭 Chrome 扩展程序

javascript - 在 uib-tab 中添加带有 active 和 ng-repeat 的选项卡

php - 是否可以在不编写脚本的情况下可靠地知道 HTTP 请求的来源?

javascript - 重置多个元素的拖动

javascript - html/css/javascript 中的动画线

javascript - 这段代码中嵌套 Promise 的目的是什么?