为什么数据不能正确投影
关键问题是您的数据已经被预测。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 中(如您在示例中找到的 svg .attr("d", d3.geoPath());
)。
此问题中的特定文件已预先投影以适合 960x600 地图,因此这非常适合零投影 - 它的设计考虑了尺寸。它的单位是像素,所有坐标都在所需的维度内。但是,大多数投影几何图形使用以米为单位的坐标系,因此要素坐标的边界框可能跨越数百万个单位。在这些情况下,空投影将不起作用 - 它会将地图单位值转换为没有缩放的像素值。
对于 d3,空投影通常与 geojson/topojson 一起使用,它使用 d3 投影预先投影以适应指定的视口。有关示例,请参见命令行制图(该示例使用未投影的源文件 - 对投影数据使用 d3 投影所产生的问题同样适用于浏览器和命令行)。预投影文件以用于空投影的主要优点是性能。
地理标识
如果您只需要缩放和居中要素,则可以使用 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 投影投影的,所以它的 y 轴已经作为 d3 投影项目翻转到 svg 或画布坐标空间。
地理身份演示
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
假设您不想更改投影类型,则使用 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 的示例:
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。不太可能是lat long对。
或者,您可以将您的数据导入到诸如 mapshaper.org 之类的在线服务,并与您知道未投影(或使用 WGS84“投影”)的另一个 topojson/geojson 进行比较。
如果处理geojson,您可能很幸运地看到定义投影的属性,例如:"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
(CRS 代表坐标参考系统)或EPSG 编号:EPSG:4326
(EPSG 代表欧洲石油调查组)。
此外,如果您的数据使用空投影而不是标准投影(缩放/缩小以确保您没有查看错误区域)进行投影,则您可能正在处理投影数据。同样,如果您的视口完全被一个特征覆盖(并且您没有放大)。NaN 坐标也是一个潜在的指标。然而,这些预测数据的最后指标也可能意味着其他问题。
最后,数据源也可能表明数据已经在元数据中或如何使用它:查看这个块,我们可以看到在geoPath
定义时没有使用任何投影。