缩放 d3 v4 地图以适合 SVG(或根本不适合)
Posted
技术标签:
【中文标题】缩放 d3 v4 地图以适合 SVG(或根本不适合)【英文标题】:Scaling d3 v4 map to fit SVG (or at all) 【发布时间】:2020-01-19 14:55:27 【问题描述】:我正在尝试缩小这张美国地图的比例。要么是我的 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 中显示已经投影的数据。
“投影”已经投影的数据
空投影:
最简单的,你可以将你的投影定义为空:
var path = d3.geoPath(null);
这将从 geojson 几何图形中获取 x,y 数据并将其显示为 x,y 数据。但是,如果您的 x,y 坐标超过了 svg 的宽度和高度,则地图将不会包含在您的 svg 中(正如您在 .attr("d", d3.geoPath());
的示例中找到的那样)。
此问题中的特定文件已预先投影以适合 960x600 地图,因此这是空投影的理想选择 - 它的设计考虑了尺寸。它的单位是像素,所有坐标都在所需的尺寸范围内。但是,大多数投影几何都使用单位为米的坐标系,因此要素坐标的边界框可能有数百万个单位。在这些情况下,null 投影将不起作用 - 它会将地图单位值转换为没有缩放的像素值。
对于 d3,空投影通常与 geojson/topojson 一起使用,它使用 d3 投影预投影以适合指定的视口。有关示例,请参见 command line cartography(该示例使用未投影的源文件 - 对投影数据使用 d3 投影产生的相同问题适用于浏览器和命令行)。预投影文件以用于空投影的主要优点是performance。
地理身份
如果您只需要对要素进行缩放和居中,则可以使用 geoIdentity。这是实现 geoTransform,但使用标准投影方法,例如 scale
、translate
,最重要的是 - fitSize
/fitExtent
。因此,我们可以将投影设置为 geoIdentity:
var projection = d3.geoIdentity();
目前这与上面使用的空投影相同,它从 geojson 几何图形中获取 x,y 数据并将其显示为 x,y 数据而不进行转换 - 将 geojson 中的每个坐标视为像素坐标。但是,我们可以将 fitSize 应用于 this(或 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 或画布坐标空间。
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:
但是,如果您的几何图形已经是平面的怎么办?也就是说,如果你只是 想要采用投影几何,但仍将其转换或缩放到 适合视口吗?
您可以实现自定义几何变换以获得完全控制 在投影过程中。
假设您不想更改投影类型,使用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));
不过,当您缩小时,这会将所有内容缩放到左上角。为了使事物居中,您可以添加一些代码来使投影居中:
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 复制一个特定的投影来匹配已经用未投影的特征投影的特征,那么这个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 pair。
或者,您可以将数据导入到在线服务,例如 mapshaper.org,并与您知道未投影(或使用 WGS84 进行“投影”)的另一个 topojson/geojson 进行比较。
如果处理geojson,你可能有幸看到定义投影的属性,例如:"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
(CRS 代表坐标参考系统)或 EPSG 编号:EPSG:4326
(EPSG 代表欧洲石油调查组)。
此外,如果您的数据项目使用空投影但不是标准投影(已按比例缩小/缩小以确保您没有在错误的区域中查看),则您可能正在处理投影数据。同样,如果您的视口完全被一个功能覆盖(并且您没有放大)。 NaN 坐标也是一个潜在的指标。然而,这些预测数据的最后指标也可能意味着其他问题。
最后,数据源还可能表明数据已经在元数据中投影或如何使用:查看此block,我们可以看到在定义geoPath
时没有使用投影。
【讨论】:
非常感谢您的帮助。我将仔细研究细节,但这提供了一个解决方案,我非常感谢! 很高兴,如果您确实有美国的未投影 geojson 或 topojson,但您不想单独关闭阿拉斯加和夏威夷,您可以使用 geoAlbersUsa 投影,它是一个复合投影这实质上是在这个问题中重新创建文件的布局。信息在:github.com/d3/d3-geo/blob/master/README.md#geoAlbersUsa以上是关于缩放 d3 v4 地图以适合 SVG(或根本不适合)的主要内容,如果未能解决你的问题,请参考以下文章