使用 D3.js 绘制自定义 json 地图
Plotting custom json maps with D3.js
我正在使用 D3.js 创建地图。
我首先在此处下载国家(加拿大)shapefile:
https://www.arcgis.com/home/item.html?id=dcbcdf86939548af81efbd2d732336db
..并在此处将其转换为 geojson(link 到下方文件):
http://mapshaper.org/
到目前为止,我所看到的只是一个彩色块,控制台上没有任何错误。我的问题是,如何判断我的 json 文件或我的代码是否不正确?
这是我的代码,底部是 link 到 json 文件。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3: Setting path fills</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<!-- <script src="https://d3js.org/topojson.v1.min.js"></script> -->
<style type="text/css">
/* styles */
</style>
</head>
<body>
<script type="text/javascript">
var canvas = d3.select("body").append("svg")
.attr("width", 760)
.attr("height", 700)
d3.json("canada.geo.json", function(data) {
var group = canvas.selectAll("g")
.data(data.features)
.enter()
.append("g")
var projection = d3.geo.mercator();
var path = d3.geo.path().projection(projection);
var areas = group.append("path")
.attr("d",path)
.attr("class","area")
})
</script>
</body>
</html>
Link 到 json 文件:
https://github.com/returnOfTheYeti/CanadaJSON/blob/master/canada.geo.json
如果您所说的正确是指它是有效的 JSON - 那么您可以简单地通过 javascript 解析它并检查是否有任何错误。
在此示例中,任何错误都将记录到控制台。否则解析的对象如果有效将被记录。
// where data is your JSON data
try {
console.log(JSON.parse(data));
} catch (e) {
console.error(e);
}
然而,由于您已经在使用 D3,因此您可以简单地使用 d3.json 方法来测试错误。向 d3 调用添加一个额外参数。
d3.json("canada.geo.json", function(error, canada) {
if (error) return console.error(error);
console.log(canada);
});
参见:https://github.com/d3/d3-3.x-api-reference/blob/master/Requests.md
d3 geoProjection 使用非投影坐标 - 三维地球上的坐标。 geoProjection 获取这些坐标并将它们投影到二维平面上。未投影坐标的单位通常是度经度和纬度,d3 geoProjection 期望这样。问题是你的数据已经被投影了。
如何判断数据是否经过投影?
有两种快速方法可以确定您的数据是否已投影:
看数据的元数据
自己看地理坐标
查看地理元数据
您的数据使用的投影在 .prj 文件中定义,该文件构成构成 shapefile 的文件集合的一部分:
PROJCS["Canada_Albers_Equal_Area_Conic",
GEOGCS["GCS_North_American_1983",
DATUM["D_North_American_1983",
SPHEROID["GRS_1980",6378137.0,298.257222101]],
PRIMEM["Greenwich",0.0],
UNIT["Degree",0.0174532925199433]],
PROJECTION["Albers"],
PARAMETER["False_Easting",0.0],
PARAMETER["False_Northing",0.0],
PARAMETER["Central_Meridian",-96.0],
PARAMETER["Standard_Parallel_1",50.0],
PARAMETER["Standard_Parallel_2",70.0],
PARAMETER["Latitude_Of_Origin",40.0],
UNIT["Meter",1.0]]
您的数据已经使用阿尔伯斯投影进行了投影,测量单位是米。将此数据投影为好像它由 lat/long 对组成将不起作用。
如果您只有一个 geojson 文件而没有参考 shapefile,一些 geojson 文件将在投影中指定一个 EPSG 数字 属性,如果这个数字不是 4326,您可能有投影数据。
看坐标
您可以看出您的数据没有未投影的数据,因为每个坐标的值都在经度和纬度范围之外(+/-180 度 east/west,+/- 90 度南北) :
"coordinates":[[[[899144.944639163,2633537.
您的坐标在全球范围内进行了多次转换:这就是为什么您的 svg 投影结果充满了特征。
好的,现在怎么办?
有两种主要解决方案可供您使用:
转换投影,使geojson由纬度和经度点组成
使用 d3.geoTransform 或 d3.geoIdentity 转换您的投影数据。
转换投影
要执行此操作,您需要“取消投影”您的数据,或者投影它以使其由经度、纬度点组成。
大多数 GIS 软件都提供重新投影数据的功能。使用 shapefile 比使用 geojson 容易得多,因为 shapefile 在 GIS 软件中更为常见。 GDAL、QGIS、ArcMap 提供相对容易的转换。
还有在线转换器,mapshaper.org 可能是最简单的,并且在处理 d3 时增加了好处 - 简化(许多 shapefile 包含太多细节用于 web 映射)。将 shapefile 的所有文件拖入 mapshaper window,打开控制台并输入:proj wgs84
。导出为 geojson(简化后),您已经为 d3 准备好一个 geojson。
重新投影后,您可能会注意到您的数据看起来很奇怪。别担心,它是未投影的(好吧,有点未投影,它显示为 2d,但有一个假设笛卡尔输入数据的非常简单的投影)。
有了未投影的数据,您现在可以在 d3 中投影数据了。
这是一个 example 你的数据(d3-v4.data 被简化并在 mapshaper 上重新投影(与我无关))
使用d3.geoIdentity或d3.geoTransform
为此我建议使用 d3v4(我看到你的代码是 v3)。虽然 geo.transform 在 v3 中可用,但如果没有 v4 中可用的新方法,即 d3.geoIdentity 和 projection.fitSize,它会更加麻烦。我将在这里讨论使用投影数据的 v4 方法
根据您的数据,您可以定义不同类型的投影:
var projection = d3.geoIdentity();
不过,这种“投射”一不小心就会出事。它基本上会吐出给定的 x,y 值。然而,地理投影坐标 spaces 通常在左下角的某个地方有 [0,0],而 svg 坐标 space 在左上角有 [0,0]。在 svg 坐标 space 中,y 值随着坐标平面的下降而增加,在数据的投影坐标 space 中,y 值随着坐标平面的上升而增加。因此,使用身份会使您的数据颠倒。
幸运的是我们可以使用:
var projection = d3.geoIdentity()
.reflectY(true);
最后一个问题仍然存在:geojson 中的坐标未缩放或平移,因此要素正确居中。为此,有 fitSize 方法:
var projection = d3.geoIdentity()
.reflectY(true)
.fitSize([width,height],geojsonObject)
这里的width和height是SVG(或者我们要在其中显示特征的父容器)的宽度和高度,geojsonObject是一个geojson特征。请注意,它不会采用一系列功能,如果您有一系列功能,请将它们放在一个功能集合中。
Here's 您采用这种方法显示的数据(我仍然简化了 geojson)。
您也可以使用 geoTransform,这有点复杂,但允许您指定自己的变换方程。在大多数情况下,它可能有点矫枉过正,坚持使用 geoIdentity。
每个选项的优缺点:
取消投影数据:
除了取消投影数据的初步工作之外,通过取消投影数据使其由经纬度对组成,您每次显示数据时都必须进行一些额外的处理。
但是,您还可以通过访问任何 d3 geoProjection 来高度灵活地显示数据。使用未投影的数据还可以让您更轻松地对齐不同的图层:您不必担心单独重新缩放和转换多个图层。
保留投影数据
通过保留数据的投影,您不必进行球形数学运算,从而节省了计算时间。缺点是上面列出的优点,很难匹配不共享此投影的数据(如果您使用此投影导出所有内容,这很好),并且您坚持表示 - d3.geoTransform 不提供将投影从 Mercator 转换为 Albers 的方式很多。
请注意,我在上面的选项二中使用的 fit.size() 方法适用于所有 geoProjections (v4)。
在这两个示例中,我尽可能使用了您的代码。不过有几点需要注意,我改为 d3v4(例如 d3.geo.path -> d3.geoPath、d3.geo.mercator -> d3.geoMercator)。我还将变量的名称 canvas
更改为 svg
,因为它是 svg 的选择而不是 canvas。最后,在第一个例子中我没有修改你的投影,墨卡托的中心默认为 [0,0](在 long/lat 中),这解释了奇怪的定位
我正在使用 D3.js 创建地图。 我首先在此处下载国家(加拿大)shapefile: https://www.arcgis.com/home/item.html?id=dcbcdf86939548af81efbd2d732336db
..并在此处将其转换为 geojson(link 到下方文件): http://mapshaper.org/
到目前为止,我所看到的只是一个彩色块,控制台上没有任何错误。我的问题是,如何判断我的 json 文件或我的代码是否不正确? 这是我的代码,底部是 link 到 json 文件。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3: Setting path fills</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<!-- <script src="https://d3js.org/topojson.v1.min.js"></script> -->
<style type="text/css">
/* styles */
</style>
</head>
<body>
<script type="text/javascript">
var canvas = d3.select("body").append("svg")
.attr("width", 760)
.attr("height", 700)
d3.json("canada.geo.json", function(data) {
var group = canvas.selectAll("g")
.data(data.features)
.enter()
.append("g")
var projection = d3.geo.mercator();
var path = d3.geo.path().projection(projection);
var areas = group.append("path")
.attr("d",path)
.attr("class","area")
})
</script>
</body>
</html>
Link 到 json 文件: https://github.com/returnOfTheYeti/CanadaJSON/blob/master/canada.geo.json
如果您所说的正确是指它是有效的 JSON - 那么您可以简单地通过 javascript 解析它并检查是否有任何错误。
在此示例中,任何错误都将记录到控制台。否则解析的对象如果有效将被记录。
// where data is your JSON data
try {
console.log(JSON.parse(data));
} catch (e) {
console.error(e);
}
然而,由于您已经在使用 D3,因此您可以简单地使用 d3.json 方法来测试错误。向 d3 调用添加一个额外参数。
d3.json("canada.geo.json", function(error, canada) {
if (error) return console.error(error);
console.log(canada);
});
参见:https://github.com/d3/d3-3.x-api-reference/blob/master/Requests.md
d3 geoProjection 使用非投影坐标 - 三维地球上的坐标。 geoProjection 获取这些坐标并将它们投影到二维平面上。未投影坐标的单位通常是度经度和纬度,d3 geoProjection 期望这样。问题是你的数据已经被投影了。
如何判断数据是否经过投影?
有两种快速方法可以确定您的数据是否已投影:
看数据的元数据
自己看地理坐标
查看地理元数据
您的数据使用的投影在 .prj 文件中定义,该文件构成构成 shapefile 的文件集合的一部分:
PROJCS["Canada_Albers_Equal_Area_Conic",
GEOGCS["GCS_North_American_1983",
DATUM["D_North_American_1983",
SPHEROID["GRS_1980",6378137.0,298.257222101]],
PRIMEM["Greenwich",0.0],
UNIT["Degree",0.0174532925199433]],
PROJECTION["Albers"],
PARAMETER["False_Easting",0.0],
PARAMETER["False_Northing",0.0],
PARAMETER["Central_Meridian",-96.0],
PARAMETER["Standard_Parallel_1",50.0],
PARAMETER["Standard_Parallel_2",70.0],
PARAMETER["Latitude_Of_Origin",40.0],
UNIT["Meter",1.0]]
您的数据已经使用阿尔伯斯投影进行了投影,测量单位是米。将此数据投影为好像它由 lat/long 对组成将不起作用。
如果您只有一个 geojson 文件而没有参考 shapefile,一些 geojson 文件将在投影中指定一个 EPSG 数字 属性,如果这个数字不是 4326,您可能有投影数据。
看坐标
您可以看出您的数据没有未投影的数据,因为每个坐标的值都在经度和纬度范围之外(+/-180 度 east/west,+/- 90 度南北) :
"coordinates":[[[[899144.944639163,2633537.
您的坐标在全球范围内进行了多次转换:这就是为什么您的 svg 投影结果充满了特征。
好的,现在怎么办?
有两种主要解决方案可供您使用:
转换投影,使geojson由纬度和经度点组成
使用 d3.geoTransform 或 d3.geoIdentity 转换您的投影数据。
转换投影
要执行此操作,您需要“取消投影”您的数据,或者投影它以使其由经度、纬度点组成。
大多数 GIS 软件都提供重新投影数据的功能。使用 shapefile 比使用 geojson 容易得多,因为 shapefile 在 GIS 软件中更为常见。 GDAL、QGIS、ArcMap 提供相对容易的转换。
还有在线转换器,mapshaper.org 可能是最简单的,并且在处理 d3 时增加了好处 - 简化(许多 shapefile 包含太多细节用于 web 映射)。将 shapefile 的所有文件拖入 mapshaper window,打开控制台并输入:proj wgs84
。导出为 geojson(简化后),您已经为 d3 准备好一个 geojson。
重新投影后,您可能会注意到您的数据看起来很奇怪。别担心,它是未投影的(好吧,有点未投影,它显示为 2d,但有一个假设笛卡尔输入数据的非常简单的投影)。
有了未投影的数据,您现在可以在 d3 中投影数据了。
这是一个 example 你的数据(d3-v4.data 被简化并在 mapshaper 上重新投影(与我无关))
使用d3.geoIdentity或d3.geoTransform
为此我建议使用 d3v4(我看到你的代码是 v3)。虽然 geo.transform 在 v3 中可用,但如果没有 v4 中可用的新方法,即 d3.geoIdentity 和 projection.fitSize,它会更加麻烦。我将在这里讨论使用投影数据的 v4 方法
根据您的数据,您可以定义不同类型的投影:
var projection = d3.geoIdentity();
不过,这种“投射”一不小心就会出事。它基本上会吐出给定的 x,y 值。然而,地理投影坐标 spaces 通常在左下角的某个地方有 [0,0],而 svg 坐标 space 在左上角有 [0,0]。在 svg 坐标 space 中,y 值随着坐标平面的下降而增加,在数据的投影坐标 space 中,y 值随着坐标平面的上升而增加。因此,使用身份会使您的数据颠倒。
幸运的是我们可以使用:
var projection = d3.geoIdentity()
.reflectY(true);
最后一个问题仍然存在:geojson 中的坐标未缩放或平移,因此要素正确居中。为此,有 fitSize 方法:
var projection = d3.geoIdentity()
.reflectY(true)
.fitSize([width,height],geojsonObject)
这里的width和height是SVG(或者我们要在其中显示特征的父容器)的宽度和高度,geojsonObject是一个geojson特征。请注意,它不会采用一系列功能,如果您有一系列功能,请将它们放在一个功能集合中。
Here's 您采用这种方法显示的数据(我仍然简化了 geojson)。
您也可以使用 geoTransform,这有点复杂,但允许您指定自己的变换方程。在大多数情况下,它可能有点矫枉过正,坚持使用 geoIdentity。
每个选项的优缺点:
取消投影数据:
除了取消投影数据的初步工作之外,通过取消投影数据使其由经纬度对组成,您每次显示数据时都必须进行一些额外的处理。
但是,您还可以通过访问任何 d3 geoProjection 来高度灵活地显示数据。使用未投影的数据还可以让您更轻松地对齐不同的图层:您不必担心单独重新缩放和转换多个图层。
保留投影数据
通过保留数据的投影,您不必进行球形数学运算,从而节省了计算时间。缺点是上面列出的优点,很难匹配不共享此投影的数据(如果您使用此投影导出所有内容,这很好),并且您坚持表示 - d3.geoTransform 不提供将投影从 Mercator 转换为 Albers 的方式很多。
请注意,我在上面的选项二中使用的 fit.size() 方法适用于所有 geoProjections (v4)。
在这两个示例中,我尽可能使用了您的代码。不过有几点需要注意,我改为 d3v4(例如 d3.geo.path -> d3.geoPath、d3.geo.mercator -> d3.geoMercator)。我还将变量的名称 canvas
更改为 svg
,因为它是 svg 的选择而不是 canvas。最后,在第一个例子中我没有修改你的投影,墨卡托的中心默认为 [0,0](在 long/lat 中),这解释了奇怪的定位