梦入琼楼寒有月,行过石树冻无烟

D3 Geo 地理路径


地理路径生成器(Geographic Path Generator),对于地图可视化,D3 支持少数的组建来显示或操作地理数据,通常使用 GeoJSON 格式(这也是在 JavaScript中的标准的地理特征表示方法,而TopoJSON则是GeoJSON的扩展)。

对于 TopoJSON、GeoJSON,可以通过使用 ogr2ogr 或 ogr2topo or geo2topo来分别进行转换的数据通过地理路径生成器生成该地图的路径值,通过使用 d3.geo.path类来直接渲染到画布中。

ID FA DA
d3.gao.path 创建一个新的地理路径生成器,可以使用默认的 abersUsa投影和 4.5个像素点半径
path(feature[,index]) 给制定的功能返回路径的数据串,可以是 GeoJSON 的特征或集合对象
Point 单个位置
MultiPoint 一组位置
LineString 一组位置形成一条连续的线
MultiLineString 位置数组的数组,形成多条线
Polygon 位置数组的数组,形成一个多边形
MultiPolygon 位置的多维数组,形成了多个多边形
GeometryCollection 几何对象的数组
Feature 包含了几何对喜爱那个其中的一个特征
FeatureCollection 特征对象的数组
path.projection([projection]) 指定投影,如果投影被有被制定,那么就会返回当前的投影默认为alberUsa(艾伯斯美国投影) 美国地图常见的就是 alberUsa 这可以使得地图更加的紧凑和可用
path.context[context] 设置渲染上下文并发那会生成路径,默认为 null,如果为空的当一个给定调用时路径生成器会返回一个SVG路径字符串。非空则路径生成器替换调用函数为指定调查上下文来渲染几何图形
path.pointRadius([radius]) 设置点半径,用于显示点(Point) 和多点(MultPoint)的功能,如果未指定则返回当前的半径
path.area(feature) 计算出指定几何体的投影面积点(Point)、多点(MultiPoint)、LineString(线串) 则面积为0,如果类型为Polygon(多边形)、MultiPolygon(多点多边形)那么先计算外部环面积,在减去内部空洞
path.centroid(feature) 对指定的功能计算几何体的中心,单位为像素
path.bounds(feature) 对特定的功能计算投影的边框

d3.geo.path

d3.geo.path 内计算方法主要分为计算面积(area)、中心(centroid)、边界框(bounds),Area 用于计算出几何体的投影面积,centroid 来计算出几何体的中心及当前选择的中心(由于使用的是 TopoJSON 他是可以做到的),bounds(投影边框)。

area and centroid as well as bounds

Area 用于计算出几何体的投影面积,centroid 来计算出几何体的中心及当前选择的中心(由于使用的是 TopoJSON 他是可以做到的),bounds(投影边框)当鼠标经过图标数据的时候显示出 area、centroid、bounds总共加起来绘制出的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
var padding = {
width: 1000,
height: 1000
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height", padding.height)

var projection = d3.geo.mercator()
.center([107,19])
.scale(600)
.translate([padding.width/2,padding.height/2])

var color = d3.scale.category20b()

var path = d3.geo.path()
.projection(projection)

d3.json("./china_topo.json", function (error, toporoot) {
if (error)
return console.error(error)
console.log(toporoot)
/*
* (1)
* 将 TopoJSON 转换为 GeoJSON 并保存在 Georoot 中。
* Feature 主要用于返回 GeoJSON 的特征(Feature)或特征集合(FeatureCollection)
* feature(topology, object) 第一额参数表示 TopJSON 文件对象,第二个参数来表示出几何体的对象。
* (2) feature
* console 分别会输出 topjson 以及 geojson 的结果。
* 所以在定义 feature() 参数是会考虑到:
* TopoJSON 对象中,有数组 arcs(共享边)、objects(存储几何对象)、 之后对象中有一个 china 对象,所以保存中国地图的几何体,因此:
* topojson.feature(toporoot,toporoot.objects.china)
*/
var georoot = topojson.feature(toporoot,toporoot.objects.china);
console.log(georoot)

var groups = svg.append("g")
groups.selectAll("path")
.data(georoot.features)
.enter()
.append("path")
.attr("class","province")
.attr("d",path)
.on("mouseover",function (d) {
/*
* 分别计算出投影面积、几何体的中心、边框
*/
var area = path.area(d)
var centroid = path.centroid(d)
var bounds = path.bounds(d)

/*
* 绘制中心点
* 绘制边框
*/
svg.append("circle")
.attr("class","circle")
.attr("cx",centroid[0])
.attr("cy",centroid[1])
.attr("r",4)

svg.append("rect")
.attr("class","rect")
.attr("x", bounds[0][0])
.attr("y",bounds[0][1])
.attr("width",bounds[1][0] - bounds[0][0])
.attr("height",bounds[1][1] - bounds[0][1])
})
})

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.province {
fill: white;
stroke: #25595a;
stroke-width: 1px;
}

.circle{
stroke: #e17676;
stroke-width: 2px;
fill: transparent;
}

.rect{
stroke: #dbdbdb;
stroke-dasharray: 10,10;
stroke-width: 2px;
fill: transparent;
}

形状生成器


形状生成器(Shape Geeratior)可以在地图上绘制网格,即经度和纬度来展现出一种视觉效果,也能让其图标先的更加专业:

ID DA FA
d3.geo.graticule() 构造一个经纬线
graticule() 返回一个类型为 MultiLineString 几何对象表示这个刻度的所有经纬线
lines() 返回一个 LineString 集合对象,用于这个刻度的为一个经线纬线
outline() 返回一个类型为 Polygon 的对象,表示出网格的轮廓
extent([extent]) 设置网格范围,如果没有指定范围,那么则返回抢钱的次要范围,默认为[[-180,-80], [180,80]]
step(step) 设置网格的主要和次要间隔,如果没有则返回(10,10)

经纬线网络

经纬线网络可以通过过使用d3.geo.path()进行绘制,在graticule中,网格的范围被使用经度范围为 -180~180,纬度范围是-90~90,每隔 10 度画一条经纬线来通过step表示间隔距离。

中国地图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
var padding = {
width: 1000,
height: 1000
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height", padding.height)

var projection = d3.geo.mercator()
.center([107,31])
.scale(600)
.translate([padding.width/2,padding.height/2])

var path = d3.geo.path()
.projection(projection)

/*
* min 是一个极小值,来防止没有没有边界线的问题
*/ var min = 1e-4;

/*
* 创建网格生成器,生成网格
* 通过最后的 grid 来进行绑定数据进行绘制。
*/
var graticule = d3.geo.graticule()
/* 经度范围为 -180~180,纬度范围是-90~90,每隔 10 度画一条经纬线*/
.extent([[-180,-90],[180+min,90]])
.step([10,10])

var grid = graticule()
svg.append("path")
.datum(grid)
.attr("class","graticule")
.attr("d",path);


d3.json("./china_topo.json", function (error, toporoot) {
if (error)
return console.error(error)
console.log(toporoot)

/*
* (1)
* 将 TopoJSON 转换为 GeoJSON 并保存在 Georoot 中。
* Feature 主要用于返回 GeoJSON 的特征(Feature)或特征集合(FeatureCollection)
* feature(topology, object) 第一额参数表示 TopJSON 文件对象,第二个参数来表示出几何体的对象。
* (2) feature
* console 分别会输出 topjson 以及 geojson 的结果。
* 所以在定义 feature() 参数是会考虑到:
* TopoJSON 对象中,有数组 arcs(共享边)、objects(存储几何对象)、 之后对象中有一个 china 对象,所以保存中国地图的几何体,因此:
* topojson.feature(toporoot,toporoot.objects.china)
*/
var georoot = topojson.feature(toporoot,toporoot.objects.china);
console.log(georoot)

var groups = svg.append("g")
groups.selectAll("path")
.data(georoot.features)
.enter()
.append("path")
.attr("class","province")
.attr("d",path)
})

CSS

1
2
3
4
5
6
7
8
9
10
11
.province {
fill: white;
stroke: #25595a;
stroke-width: 1px;
}

.graticule {
fill: transparent;
stroke: #e2e2e2;
stroke-width: 1px;
}

世界地图

地图JSON: https://gitee.com/mirrors_jamiebuilds/world.geo.json/blob/master/countries.geo.json#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
var padding = {
width: 1000,
height: 1000
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height", padding.height)

var projection = d3.geo.mercator()
.center([0,0])
.scale(120)
.translate([padding.width/2,padding.height/2])

var path = d3.geo.path()
.projection(projection)

/*
* min 是一个极小值,来防止没有没有边界线的问题
*/ var min = 1e-4;

/*
* 创建网格生成器,生成网格
* 通过最后的 grid 来进行绑定数据进行绘制。
*/
var graticule = d3.geo.graticule()
/* 经度范围为 -180~180,纬度范围是-90~90*/
.extent([[-180,-90],[180+min,90]])
.step([10,10])

var grid = graticule()
svg.append("path")
.datum(grid)
.attr("class","graticule")
.attr("d",path);


/*
* 绘制地图
*/
d3.json("./world-continents.geo.json", function (error, root) {
if (error)
return console.error(error)

var groups = svg.append("g")
groups.selectAll("path")
.data(root.features)
.enter()
.append("path")
.attr("class","province")
.attr("d",path)
})

圆形网络


除了绘制经纬线网络之外,还有原型网络的绘制,同样是通过使用d3.geo.path()进行绘制,地图和网格都会自动进行适应,需要注意的是如果出现问题那基本上就是地图数据的原因,目前支持绘制圆形网络的生成器方法如下:

ID DA FA
d3.geo.circle 为一个指定的地理位置创建带有设置的半径,在圆中心创建一个特征生成器
circle(arguments) 返回一个类型为多边形的 GeoJSON 对象
circle.origin([origin]) 设置圆的圆点,默认值为 (0,0)
circle.precision([precision]) 设置圆半径,默认为90度

圆形网络是一个分别在平面地图和球面地图,平面主要是将球面地图展开后进行的结果,我们可以使用d3.geo.mercator(),来绘制出平面图,而通过使用d3.geo.orthographic,来绘制出球形网络。

ID DA FA
d3.gao.orthographic() 来显示出适合单个半球的投影,即球面投影
rotation([rotate]) 指定旋转,设置投影的三个轴的指定角度
clipAngle(angle) 设置投影和裁剪圆的半径,指定角度并返回。如果角度为 null则切换到子午线切割,而不是小圆裁剪。(如果没有指定角度,则发那会出当前的裁剪角度,默认为空)

mercator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
var padding = {
width: 1000,
height: 1000
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height", padding.height)

var projection = d3.geo.mercator()
.center([0,0])
.scale(120)
.translate([padding.width/2,padding.height/2])

var path = d3.geo.path()
.projection(projection)

/*
* min 是一个极小值,来防止没有没有边界线的问题
*/ var min = 1e-4;


var angles = d3.range(0,150,8)
var geocircle = d3.geo.circle()
.origin([77,-19])

svg.append("g")
.selectAll(".geocircle")
.data(angles)
.enter()
.append("path")
.attr("class","graticule")
.attr("d", function (d) {
var circle = geocircle.angle(d)
return path(circle())
})

/*
* 绘制地图
*/
d3.json("./world-continents.geo.json", function (error, root) {
if (error)
return console.error(error)

var groups = svg.append("g")
groups.selectAll("path")
.data(root.features)
.enter()
.append("path")
.attr("class","province")
.attr("d",path)
})

orthographic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
var padding = {
width: 1000,
height: 1000
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height", padding.height)

var projection = d3.geo.orthographic()
.center([0,0])
.scale(300)
.rotate([-90,-20])
.clipAngle(90)
.translate([padding.width/2,padding.height/2])

var path = d3.geo.path()
.projection(projection)

/*
* min 是一个极小值,来防止没有没有边界线的问题
*/ var min = 1e-4;


var angles = d3.range(0,150,8)
var geocircle = d3.geo.circle()
.origin([77,-19])

svg.append("g")
.selectAll(".geocircle")
.data(angles)
.enter()
.append("path")
.attr("class","graticule")
.attr("d", function (d) {
var circle = geocircle.angle(d)
return path(circle())
})

/*
* 绘制地图
*/
d3.json("./world-continents.geo.json", function (error, root) {
if (error)
return console.error(error)

var groups = svg.append("g")
groups.selectAll("path")
.data(root.features)
.enter()
.append("path")
.attr("class","province")
.attr("d",path)
})
⬅️ Go back