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

D3 地图投影

在 D3 中 创建投影的方式大多数也一定是通过d3.geo.projection来进行创建的,D3 为此包含了很多现成的投影方法,如albersUsa()、conicConformal()、conicEqualArea()、conicEqualArea.parallels()等投影,但最常用的可能也就是d3.geo.projection & d3.geo.orthographic()两种投影等。

ID DA FA
d3.geo.projection(raw) 从制定的原始点投影函数,创建一个投影
projection(location) 将球面坐标系坐标投影到笛卡尔坐标系的坐标,(location是一个数组,返回数组[x,y],输入数组是形式为[[经度],[纬度]]
projection.invert(point) 从直角坐标(像素)反响投影到球面坐标(度)返回一个数组[经度,纬度],分别表示[x,y]但并非有所有的投影都会被反转
projection.rotate(rotation) 设置渲染,rotation 数组是[yaw, pitch, roll],其中yew 表示左右,pitch 表示倾斜度,而 roll 表示转动
projection.center([location]) 设置投影中心,如果设置了中心点。(location 数组,格式为[经度、纬度]) ,默认为(0,0)
projection.translate(point) 设置投影的平移,如果 point 点被指定,则设置位移为经度,和维度的两元数组并发那会投影,默认为 [480,250]
projection.scale([scale]) 设置投影的比例尺,如设置 scale则返回投影,即缩放因子,未指定投影则默认[150]
projection.clipAngle(angle) 设置圆的裁剪角度,单位为度
projection.clipExtent(extent) 设置矩形裁剪框,extent 的格式为[[x0,y0], [x1,y1]],分别表示
x0 视窗的左边界
y0 上方
x1 右方
y1 下方

地图投影

墨卡托投影 (d3.geo.mercator)

简单理解就是墨卡托投影是将圆柱展开平面后得到的投影,而这个投影的形成就是在地球内部中心放个点光源,之后光线会将地表中每一个位置都投影到柱面上,展开后就是世界地图。

墨卡托投影(mercator),是正轴等角圆柱投影,由荷兰地理学家墨卡托(G.Mercator)1569年创立。他假想了一个地轴方向一致的圆柱或切割于地球。当圆柱展为平面后,得到本投影(通常横是纬线,竖经线)。

需要注意的是墨卡托投影并不是真实的世界样貌,由于地球是三维球体,将完整展现在二维平面是需要牺牲些真实性。所以墨卡托选择牺牲了面积大小,保留角度和形状

因此上图将地球切为12份,然后将边缘的切口进行连接,那么只有赤道的点互相连接,就会造成远离赤道的距离越大,就会显得面积较广,而在 D3 中的API 手册中也有类似的建议

这个球面的Mercator投影是常用的分片式映射库(例如OpenLayers 和Leaflet)。例如显示栅格分片与Mercator投影,可以参见d3.geo.tile插件,它是正形投影的,然而,它的推行造成了世界范围地区严重失真,因此不建议使用choropleths。

等距投影 (d3.geo.equirectangular)


等距投影(equirectangular)是一种简单的地图投影方法,该投影的经线和纬线都是互相垂直且等距的。在这种投影方法中,假设了球面和圆柱面相切于赤道,将球面的经纬线投影到圆柱面上,之后沿着圆柱的一条母线所开展平面的一种投影

正射投影(d3.geo.orthographic)

正射投影(orthographic)适用与投射单个半球的方位投影,属于任意性质的透视方位投影。

立体投影(d3.gao.stereographic)


立体投影(stereigraohic)是另一个角度(方位)的投影。在极方位上,经线是极点的直线,所有的直线间的夹角都是根据正北来进行确定。纬线都显示为同心圆弧,其间距都是从极点来进行增加,交点均为90度,无法显示另一极点

立体投影是一种等角地图投影,他不会保持真实的方向,但是角度和形状将会保持最小比例。可以使用标准纬线或比例因子参数来指定比例畸变圆弧。面积和距离和比例的畸变会随着标准纬线的距离增加而迅速增大。

球心投影/心射极平投影(d3.geo.gnomonic)


球心投影(d3.gao.gnomonic)是方位投影之一,以球心O为投射中心,他是最有用的投影方法之一,也被称之为心射极平投影,来表示出一种物体的上的点、面、与角距关系的平面投影,投影面与投影球的北极相切。

圆锥等距投影(d3.geo.conicEquidistant)

圆锥等距投影

圆锥等距投影(conicEquidistant)或等距圆锥投影保留了所有经线和两个标准纬线之间的距离。该投影方法通常作为兰勃特等角圆锥和阿尔伯斯等积圆锥投影的一种折衷投影。

该投影最适用与在中纬度东西分布的大陆板块,基本投影形式由 Claudius Ptolemy 于公元100年最初提出,后盖被进行各种改进,最大的改进由 Nicolas d3’ l’lsle 于 1745年进行修改。

兰勃特地图投影
兰勃特等角堆地图投影由 Johann H.Lambert 于1772年开发,该投影主要基于两个标准纬线,但也可以使用单个标准纬线和比例因子对其进行定义(适用与中纬度东西方向分布的大陆进行角制图,第一次世界大战前很少使用,目前全世界的官方地图制图通常使用该投影

阿尔伯斯地图投影
阿尔伯斯地图投影是一种等积圆锥投影,该圆锥投影使用两条标准纬线,相比仅使用一条标准纬线的投影可以从某种程度上减少畸变。该投影主最适用与中纬度东西方向分布的大陆板块,最早由 Heinrich C. Albers 于 1805年提出,椭圆方程被Oscar S.Adams 于1927年开发。

各个参数

center() and scale as well as translate


在下述的 code 中,将地图中心点设置为台湾省,坐标为121.56372070312499,25.035838555635017,缩放因子/比例尺为600,之后通过平移使得原点位与 SVG 的区域中心即padding.width/2, padding.height/2

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
var padding = {
width: 1000,
height: 1000
}

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

/*
* center
* 用于设置中心点,本次设置的中心点为 台湾省
* scale
* 设置缩放因子为600,
* translate
* 以 svg 区域为中心为坐标的原点
*/
var projection = d3.geo.equirectangular()
.center([121.56372070312499,25.035838555635017])
.scale(600)
.translate([padding.width/2, padding.height/2])

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

/*
* 绘制地图
*/
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)

/*
* rect 为轮廓
* line 为 x/y 轴
*/
svg.append("rect")
.attr("class","border")
.attr("x",0)
.attr("y",0)
.attr("width",padding.width)
.attr("height",padding.height)

svg.append("line")
.attr("class","axis")
.attr("x1",0)
.attr("y1",padding.height/2)
.attr("x2",padding.width)
.attr("y2",padding.height/2)

svg.append("line")
.attr("class","axis")
.attr("x1",padding.width/2)
.attr("y1",0)
.attr("x2",padding.width/2)
.attr("y2",padding.height)
})

CSS

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

.border {
fill: transparent;
stroke: black;
stroke-width: 1px;
}
.axis {
fill: transparent;
stroke: rgb(255,0,255);
stroke-width: 0.7px;
stroke-dasharray: 5,5;
}

projection and invert

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
72
73
74
75
76
77
78
79
80
81
82

```js
var padding = {
width: 1000,
height: 1000
}

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

/*
* center
* 用于设置中心点,本次设置的中心点为 台湾省
* scale
* 设置缩放因子为600,
* translate
* 以 svg 区域为中心为坐标的原点
*/
var projection = d3.geo.equirectangular()
.center([121.56372070312499,25.035838555635017])
.scale(800)
.translate([padding.width/2, padding.height/2])

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

/*
* 绘制地图
*/
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)

/*
* rect 为轮廓
* line 为 x/y 轴
*/
svg.append("rect")
.attr("class","border")
.attr("x",0)
.attr("y",0)
.attr("width",padding.width)
.attr("height",padding.height)

svg.append("line")
.attr("class","axis")
.attr("x1",0)
.attr("y1",padding.height/2)
.attr("x2",padding.width)
.attr("y2",padding.height/2)

svg.append("line")
.attr("class","axis")
.attr("x1",padding.width/2)
.attr("y1",0)
.attr("x2",padding.width/2)
.attr("y2",padding.height)

/*
(1)
projection与invert前者是投影到球面坐标之后转到笛卡尔坐标,
并返回数组[x,y],而invert则是投影反响直角坐标系到球面坐标中。
*/
var projectioncircle = projection([121.56372070312499,25.035838555635017])
svg.append("circle")
.attr("cx",projectioncircle[0])
.attr("cy",projectioncircle[1])
.attr("r",3)
.style("fill","red")

var pos = projection.invert(projectioncircle)
})

clipExtent

设置为:```[300,0],[padding.width,padding.height]```,就可以进行裁剪左侧的地图数据:
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85

```js
var padding = {
width: 1000,
height: 1000
}

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

/*
* center
* 用于设置中心点,本次设置的中心点为 台湾省
* scale
* 设置缩放因子为600,
* translate
* 以 svg 区域为中心为坐标的原点
* clipExtent
* projection.clipExtent()主要表示设置一个矩形裁剪框,其格式可以为[x0,y0],[x1,y1]
*/
var projection = d3.geo.equirectangular()
.clipExtent([[300,0],[padding.width,padding.height]])
.center([121.56372070312499,25.035838555635017])
.scale(200)
.translate([padding.width/2, padding.height/2])

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

/*
* 绘制地图
*/
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)

/*
* rect 为轮廓
* line 为 x/y 轴
*/
svg.append("rect")
.attr("class","border")
.attr("x",0)
.attr("y",0)
.attr("width",padding.width)
.attr("height",padding.height)

svg.append("line")
.attr("class","axis")
.attr("x1",0)
.attr("y1",padding.height/2)
.attr("x2",padding.width)
.attr("y2",padding.height/2)

svg.append("line")
.attr("class","axis")
.attr("x1",padding.width/2)
.attr("y1",0)
.attr("x2",padding.width/2)
.attr("y2",padding.height)

/*
(1)
projection与invert前者是投影到球面坐标之后转到笛卡尔坐标,
并返回数组[x,y],而invert则是投影反响直角坐标系到球面坐标中。
*/
var projectioncircle = projection([121.56372070312499,25.035838555635017])
svg.append("circle")
.attr("cx",projectioncircle[0])
.attr("cy",projectioncircle[1])
.attr("r",3)
.style("fill","red")

var pos = projection.invert(projectioncircle)
})

rotate and clipAngle


这里介绍下在正射投影orthographic()中较为常用的两个方法为rotate and clipAngle,分别表示了旋转角度(rotate)和裁剪角度(clipAngle),如果不清楚的读者可以看下图:

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()
.scale(400)
.translate([padding.width/2, padding.height/2])
/*
* rotate
* 有三个角度,分别为左右、倾斜度、转动等,[yaw, pitch, roll]
* clipAngle
* 即裁剪骄傲读为90:参数别为 30°(左上)、60°(右上)、90°(左下)、180°(右下)
*/
.rotate([-120 ,-20, 0])
.clipAngle(90)

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

/*
* graticule
* 返回一个多重链接(MultiLineString)几何对象表示刻度尺的所有经线和纬线
* extent
* 如果指定则设置刻度尺的主要和次要范围,默认为[[-180,-80],[180,80]]
* step
* 设置刻度的主要和次要步长,及间隔,默认为 [10,10]
*/
var graticule = d3.geo.graticule()
.extent([[-180,-90],[180,90]])
.step([10,10])

svg.append("path")
.attr("class","graticule")
.attr("d",path(graticule()))
/*
* 绘制地图
*/
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)
})

CSS

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

.graticule {
fill: transparent;
stroke: #dddddd;
stroke-width: 1px;
}
⬅️ Go back