给定 EPSG 投影(例如,这个阿拉巴马州的投影:[ http://spatialreference.org/ref/epsg/26729/][1])
如何以可以在 D3.js 投影中使用它们的方式获取给定的 WGS84 投影边界。
例如,您如何知道使用什么投影、旋转度数或边界框来显示地图?
给定 EPSG 投影(例如,这个阿拉巴马州的投影:[ http://spatialreference.org/ref/epsg/26729/][1])
如何以可以在 D3.js 投影中使用它们的方式获取给定的 WGS84 投影边界。
例如,您如何知道使用什么投影、旋转度数或边界框来显示地图?
这是一个相当复杂的问题。答案将根据您正在查看的空间参考(SRS,或坐标参考系统 (CRS))系统以及您的最终目标而有所不同。
我在这个答案中使用 d3.js v4
例如,您如何知道使用什么投影、旋转度数或边界框来显示地图?
没有一套涵盖所有预测的硬性规则。查看投影参数通常可以为您提供足够的信息来快速创建投影 - 假设投影在 d3 中是开箱即用的。
我可以在设置参数时给出的最佳建议,例如何时旋转或何时居中,使用什么平行线等,是在优化投影时缩小路径,以便您可以看到每个参数在做什么以及您正在寻找的位置。然后进行缩放或范围拟合。那并为您的边界框使用geojson验证器,就像这个。
最后,您总是可以使用投影数据并完全删除 d3.geoProjection (这个问题),如果您的所有数据都已经投影在同一个投影中,那么尝试定义投影是一个有争议的问题。
我会很快注意到,如果您查看基准之间的差异,问题可能会更加复杂。例如,您引用的 SRS 使用了NAD27 基准。基准面是地球形状的数学表示,NAD27 与NAD83或WGS84不同,尽管都以度数为单位,因为基准面代表地球的三维表面。如果您正在混合使用冲突基准的数据,则可能会遇到一些精度问题,例如 NAD27 和 NAD83 之间的基准偏移并非不重要,具体取决于您的需要(维基百科屏幕截图,无法链接到图像):
如果由于使用多个基准而导致位置偏移是一个问题,您需要超过 d3 才能将它们转换为一个标准基准。D3 假设您将使用 WGS84,即 GPS 系统使用的基准。如果这些转变不是问题,那么请忽略这部分答案。
那么,让我们看看您的投影EPSG:26729:
PROJCS["NAD27 / Alabama East",
GEOGCS["NAD27",
DATUM["North_American_Datum_1927",
SPHEROID["Clarke 1866",6378206.4,294.9786982138982,
AUTHORITY["EPSG","7008"]],
AUTHORITY["EPSG","6267"]],
PRIMEM["Greenwich",0,
AUTHORITY["EPSG","8901"]],
UNIT["degree",0.01745329251994328,
AUTHORITY["EPSG","9122"]],
AUTHORITY["EPSG","4267"]],
UNIT["US survey foot",0.3048006096012192,
AUTHORITY["EPSG","9003"]],
PROJECTION["Transverse_Mercator"],
PARAMETER["latitude_of_origin",30.5],
PARAMETER["central_meridian",-85.83333333333333],
PARAMETER["scale_factor",0.99996],
PARAMETER["false_easting",500000],
PARAMETER["false_northing",0],
AUTHORITY["EPSG","26729"],
AXIS["X",EAST],
AXIS["Y",NORTH]]
这是一个非常标准的投影描述。每种类型的投影都有特定的参数,因此这些参数并不总是相同的。
此描述中最重要的部分是:
NAD27 / Alabama East
投影名称,不需要,但一个很好的参考,因为它比 EPSG 编号更容易记住,并且参考/工具可能只使用通用名称而不是 EPSG 编号。
PROJECTION["Transverse_Mercator"]
我们正在处理的投影类型。这定义了表示地球表面上的点的 3d 坐标如何转换为笛卡尔平面上的 2d 坐标。如果您在此处看到未在 d3 支持的投影列表 ( v3 - v4 ) 中列出的投影,那么您在定义自定义投影时需要做一些工作。但是,通常,您会找到与此匹配的投影。无论地图是在每个轴上旋转还是居中,投影的类型都会发生变化。
PARAMETER["latitude_of_origin",30.5],
PARAMETER["central_meridian",-85.83333333333333],
这两个参数设置投影的中心。对于横向墨卡托,只有中央经线很重要。请参阅此演示在横向墨卡托上选择中央经线的效果。
原纬度主要用于设置北纬的参考点。中央子午线也适用于东向线,但如上所述,设置中央子午线,其中从极到极的失真最小化(它相当于常规墨卡托上的赤道)。如果您确实需要正确的北距和东距以便您可以比较纸质地图和共享相同投影的网络地图中的 x,y 位置,那么 d3 可能不是最好的工具。如果您不关心在笛卡尔坐标空间中测量坐标,则这些参数无关紧要:D3 不复制投影的坐标系(以英尺为单位测量为假东/北),而是在 SVG 坐标中复制相同的形状空间。
因此,根据投影描述中的相关参数,以该投影原点为中心的 d3.geoProjection 如下所示:
d3.geoTransverseMercator()
.rotate([85.8333,0])
.center([0,30.5])
为什么我旋转了大约 86 度?这就是横向墨卡托的构建方式。在横向墨卡托的演示中,地图沿 x 轴旋转。以 x 轴为中心只会左右平移地图,不会改变投影的性质。在演示中,很明显投影正在经历与平移根本不同的变化,这是正在应用的旋转。当我在投影下转动地球时,我使用的旋转是负的。所以这个投影的中心是 -85.833 度或西经 85.8333 度。
由于在横轴墨卡托上,沿子午线的失真是一致的,因此我们可以向上平移而无需旋转。这就是为什么我在 y 轴上使用中心(在这种情况下和其他情况下,您也可以在 y 轴上旋转,y 为负值,因为这将旋转地图下方的圆柱投影,产生与平移相同的结果) .
如果我们将其缩小一点,这就是投影的样子:
它可能看起来很扭曲,但它仅用于显示阿拉巴马州及其附近的区域。放大它开始看起来更正常:
下一个问题自然是:规模呢?嗯,这将根据您的视口大小和您要显示的区域而有所不同。而且,您的投影没有指定任何界限。如果您想显示地图投影的范围,我将在答案末尾触及界限。即使投影有边界,它们也很可能与您要显示的区域不对齐(这通常是整体投影边界的子集)。
以其他地方为中心呢?假设您只想显示一个并非恰好位于投影中心的城镇?好吧,我们可以使用中心。因为我们在 x 轴上旋转了地球,所以任何居中都是相对于中央子午线的。以 [1,30.5] 为中心,将使地图以中央经线以东 1 度(西 85.8333 度)为中心。所以 x 分量将相对于旋转,y 分量将相对于赤道 - 它的纬度)。
如果坚持投影很重要,则需要这种奇怪的居中行为,如果不需要,则简单地修改 x 旋转可能会更容易,这样您就可以得到如下所示的投影:
d3.geoTransverseMercator()
.center([0,y])
.rotate([-x,0])
...
这将自定义横向墨卡托以针对您的特定区域进行优化,但代价是偏离您的起始投影。
不同的投影可能有不同的参数。例如,圆锥投影可以有一条(切线)或两条(割线)线,这些线表示投影与地球相交的点(因此失真最小化)。这些投影(例如 Albers 或 Lambert Conformal)使用类似的居中方法(旋转 -x,中心 y),但具有附加参数来指定表示切线或割线的平行线:
d3.geoAlbers()
.rotate([-x,0])
.center([0,y])
.parallels([a,b])
有关如何旋转/居中 Albers 的信息,请参阅此答案(对于目前想到的所有锥形投影而言,这基本上是相同的)。
平面/方位角投影(我尚未检查)可能仅居中。但是,每个地图投影在“居中”它的方法可能略有不同(通常是 .rotate 和 .center 的组合)。
关于如何设置不同的投影类型/系列有很多示例和 SO 问题,这些应该有助于大多数特定的投影。
但是,您可能有一个指定边界的投影。或者更有可能是带有 bounds 和 projection 的图像。在这种情况下,您将需要指定这些界限。这最容易通过 geojson 功能使用.fitExtent
a 的方法完成d3.geoProjection()
:
投影.fitExtent(范围,对象):
设置投影的比例并平移以适应给定范围中心的指定 GeoJSON 对象。范围指定为数组 [[x₀, y₀], [x₁, y₁]],其中 x₀ 是边界框的左侧,y₀ 是顶部,x₁ 是右侧,y₁ 是底部。返回投影。
(另请参阅此问题/答案)
我将使用此处问题中的示例来演示使用边界框来帮助定义投影。目标是使用以下知识投影下面的地图:它的投影和它的边界框(我很方便,但找不到足够快的定义边界框的好例子):
然而,在我们到达边界框坐标之前,让我们看一下投影。在这种情况下,它类似于:
PROJCS["ETRS89 / Austria Lambert",
GEOGCS["ETRS89",
DATUM["European_Terrestrial_Reference_System_1989",
SPHEROID["GRS 1980",6378137,298.257222101,
AUTHORITY["EPSG","7019"]],
AUTHORITY["EPSG","6258"]],
PRIMEM["Greenwich",0,
AUTHORITY["EPSG","8901"]],
UNIT["degree",0.01745329251994328,
AUTHORITY["EPSG","9122"]],
AUTHORITY["EPSG","4258"]],
UNIT["metre",1,
AUTHORITY["EPSG","9001"]],
PROJECTION["Lambert_Conformal_Conic_2SP"],
PARAMETER["standard_parallel_1",49],
PARAMETER["standard_parallel_2",46],
PARAMETER["latitude_of_origin",47.5],
PARAMETER["central_meridian",13.33333333333333],
PARAMETER["false_easting",400000],
PARAMETER["false_northing",400000],
AUTHORITY["EPSG","3416"],
AXIS["Y",EAST],
AXIS["X",NORTH]]
由于我们将让 d3 根据边界框选择比例和中心点,因此我们只关心几个参数:
PARAMETER["standard_parallel_1",49],
PARAMETER["standard_parallel_2",46],
这是两条割线,地图投影与地球表面相交。
PARAMETER["central_meridian",13.33333333333333],
这是中央子午线,我们将用于沿 x 轴旋转投影的数字(就像想到的所有圆锥投影一样)。
最重要的是:
PROJECTION["Lambert_Conformal_Conic_2SP"],
这条线为我们提供了我们的投影系列/类型。
总而言之,这给了我们类似的东西:
d3.geoConicConformal()
.rotate([-13.33333,0]
.parallels([46,49])
现在,由这些限制定义的边界框:
( .fitExtent
and .fitSize
) 方法采用 geojson 对象并适当地转换和缩放投影。 我将.fitSize
在这里使用它,因为它会跳过边界周围的边距(fitExtent
允许提供边距,这是唯一的区别)。所以我们需要创建一个具有这些边界的 geojson 对象:
var bbox = {
"type": "Polygon",
"coordinates": [
[
[9.3, 49.2], [17.2, 49.2], [17.2, 46], [9.3, 46], [9.3,49.2]
]
]
}
记住使用右手法则,并且让你的终点与你的起点相同(否则无尽的悲伤)。
现在我们所要做的就是调用这个方法,我们就会有我们的投影。因为我使用图像来验证我的投影参数,所以我知道我想要的纵横比。如果您不知道纵横比,则可能有一些多余的 width 或 height。这给了我类似的东西:
var projection = d3.geoConicConformal()
.parallels([46,49])
.rotate([-13.333,0])
.fitSize([width,height],bbox)
还有一个看起来很开心的最终产品,比如(记住一个严重下采样的世界 topojson):