缩放d3 v4地图以适合SVG(或根本不适合)

Jas*_*ins 11 javascript svg geojson d3.js topojson

我试图让这个美国规模的地图变小.要么是我的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());
    });
}
Run Code Online (Sandbox Code Playgroud)

我尝试过类似的东西:

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

但每次我在路径变量中添加任何东西时,我都会从D3的内部部分得到NAN错误.谢谢你的帮助!

And*_*eid 14

为什么数据无法正常投影

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

您的数据不符合这一点 - 它已经是平面的.你可以看到最明显的原因是因为阿拉斯加不应该在那里(除非有人改变了阿拉斯加的长对子,这是不太可能的).已投影数据的其他迹象和症状可能是涵盖整个星球的特征,以及NaN误差.

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

"投影"已经预测了数据

空投影:

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

var path = d3.geoPath(null);
Run Code Online (Sandbox Code Playgroud)

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

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

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

geoIdentity

如果您只需要缩放和居中功能,则可以使用geoIdentity.这实现了一个几何图形变换但是具有标准投影方法,如scale,translate以及最重要的- fitSize/ fitExtent.因此,我们可以将投影设置为地理位置:

var projection = d3.geoIdentity();
Run Code Online (Sandbox Code Playgroud)

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

var projection = d3.geoIdentity()
  .fitSize([width,height],geojsonObject);
Run Code Online (Sandbox Code Playgroud)

要么

var projection = d3.geoIdentity()
  .fitExtent([[left,top],[right,bottom]], geojsonObject);
Run Code Online (Sandbox Code Playgroud)

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

var projection = d3.geoIdentity()
 .fitExtent([width,height],geojsonObject)
 .reflectY(true);
Run Code Online (Sandbox Code Playgroud)

这个特殊的数据集:https : //d3js.org/us-10m.v1.json是用d3投影投影的,因此当d3投影投影到svg或画布坐标空间时,其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);
  
});
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

地理坐标

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

来自Mike Bostock:

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

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

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));
Run Code Online (Sandbox Code Playgroud)

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

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))
Run Code Online (Sandbox Code Playgroud)

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);
  
});
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

取消投影数据

在某些情况下,此方法很有用.但它需要您知道用于创建数据的投影.使用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);
});
Run Code Online (Sandbox Code Playgroud)

您将很快看到值超过+/- 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坐标也是一个潜在的指标.但是,这些预测数据的最后指标也可能意味着其他问题.

最后,数据源还可以指示数据已经在元数据或其使用方式中进行了预测:查看此,我们可以看到在geoPath定义时未使用任何投影.