将 EPSG 投影范围转换为 D3.js 地图

Converting EPSG projection bounds to a D3.js map

给定 EPSG 投影(例如,这个阿拉巴马州投影:[http://spatialreference.org/ref/epsg/26729/][1]

如何采用给定的 WGS84 投影边界,以便在 D3.js 投影中使用它们。

例如,您如何知道要使用什么投影、旋转度数或边界框来显示地图?

这是一个相当复杂的问题。答案将根据您正在查看的空间参考(SRS,或坐标参考系统 (CRS))系统以及您的最终目标而有所不同。

我在这个答案中使用 d3.js v4

简答:

For example, how would you know what projection, degree of rotation or bounding box to use to show the map?

没有涵盖所有预测的硬性规定。查看投影参数通常可以为您提供足够的信息来快速创建投影 - 假设投影在 d3 中开箱即用。

关于设置参数(例如何时旋转或何时居中、使用什么平行线等)我能给出的最佳建议是在细化投影时缩小方式,以便您可以看到每个参数的作用以及你在看哪里。然后进行缩放或范围拟合。那并为您的边界框使用 geojson 验证器,例如 this one

最后,您始终可以使用投影数据并完全删除 d3.geoProjection (),如果您的所有数据都已投影到同一投影中,则尝试定义投影是一个有争议的问题.


基准面

我会很快注意到,如果您查看数据之间的差异,问题可能会变得更加复杂。例如,您引用的 SRS 使用 NAD27 datum. A datum is a mathematical representation of the earth's shape, NAD27 will differ from NAD83 or WGS84,尽管所有的测量单位都是度数,因为基准代表地球的三维表面。如果您正在混合使用冲突基准的数据,您可能会遇到一些精度问题,例如,根据您的需要,NAD27 和 NAD83 之间的基准偏移并非微不足道(维基百科截图,无法 link 图像):

如果由于使用多个基准而导致位置偏移成为一个问题,您将需要不止 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 编号更容易记住,并且 references/tools 可能只使用通用名称而不是 EPSG 编号。

PROJECTION["Transverse_Mercator"] 我们正在处理的投影类型。这定义了如何将表示地球表面上的点的 3d 坐标转换为笛卡尔平面上的 2d 坐标。如果您在此处看到的投影未列在 d3 支持投影列表 (v3 - v4) 中,那么您需要做一些工作来定义自定义投影。但是,通常,您会找到与此匹配的投影。无论地图是在每个轴上旋转还是居中,投影类型都会发生变化。

PARAMETER["latitude_of_origin",30.5],
PARAMETER["central_meridian",-85.83333333333333],

这两个参数设置投影的中心。对于横轴墨卡托,只有中央子午线是重要的。 See this demo of the effect of choosing a central meridian on a transverse Mercator

原点纬度主要用来给北方人设定一个参考点。中央子午线对东距也是如此,但如上所述,中央子午线设置了从两极到另一极的失真最小的中央子午线(它相当于常规墨卡托上的赤道)。 如果您确实需要正确的北距和东距,以便您可以比较纸质地图和共享相同投影的网络地图的 x,y 位置,d3 可能不是执行此操作的最佳工具。如果您不关心测量笛卡尔坐标 space 中的坐标,这些参数无关紧要:D3 不复制投影的坐标系(以英尺为单位测量为 false eastings/northings)但正在复制SVG 坐标中的相同形状 space.


因此根据投影描述中的相关参数,以投影原点为中心的 d3.geoProjection 看起来像:

d3.geoTransverseMercator()
    .rotate([85.8333,0])
    .center([0,30.5])

为什么我大约旋转了 86 度?这就是横向墨卡托的构建方式。在 demo of a transverse Mercator, the map is rotated along the x axis. Centering on the x axis will simply pan the map left and right and not change the nature of the projection. In the demo 中,很明显投影正在发生与平移根本不同的变化,这是正在应用的旋转。当我在投影下转动地球时,我使用的旋转是负的。所以这个投影的中心是 -85.833 度或西经 85.8333 度。

由于在横轴墨卡托上,变形沿着子午线是一致的,我们可以向上向下平移而不需要旋转。这就是为什么我在 y 轴上使用中心(在这种情况下,在其他情况下,您也可以在 y 轴上旋转,y 为负值,因为这将在地图下方旋转圆柱投影,从而与平移相同的结果).

如果我们稍微缩小一点,这就是投影的样子:

[=40=

它可能看起来很扭曲,但它只是为了显示阿拉巴马州及其附近的区域。放大它开始看起来更正常了:

下一个问题自然是:规模呢?那么这将根据您的视口大小和您要显示的区域而有所不同。而且,您的投影没有指定任何界限。如果您想显示地图投影的范围,我将在答案末尾提及边界。即使投影有边界,它们也可能不会与您要显示的区域对齐(通常是整个投影边界的子集)。

在别处居中怎么样?假设您只想显示一个并不恰好位于投影中心的城镇?好吧,我们可以使用中心。因为我们在 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])

See this answer on how to rotate/center an Albers(目前想到的所有圆锥形投影基本相同)。

A planar/azimuthal projeciton (我还没有检查过) 可能只是居中。但是,每个地图投影在 'centering' 中的方法可能略有不同(通常是 .rotate 和 .center 的组合)。

有很多关于如何设置不同投影的示例和 SO 问题 types/families,这些应该有助于大多数特定的投影。


边界框

但是,您可能有一个指定边界的投影。或者更可能是 。在这种情况下,您需要指定这些界限。使用 d3.geoProjection():

.fitExtent 方法,使用 geojson 功能最容易做到这一点

projection.fitExtent(extent, object):

Sets the projection’s scale and translate to fit the specified GeoJSON object in the center of the given extent. The extent is specified as an array [[x₀, y₀], [x₁, y₁]], where x₀ is the left side of the bounding box, y₀ is the top, x₁ is the right and y₁ is the bottom. Returns the projection.

(另见

我将使用问题 to demonstrate the use of a bounding box to help define a projection. The goal will be to project the map below 中的示例并了解以下知识:它的投影和边界框(我有它在手边,但无法快速找到具有已定义边界框的好示例够了):

然而,在我们到达边界框坐标之前,让我们看一下投影。在这种情况下,它是 like:

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"],

这条线给出了我们的投影 family/type。

总而言之,这给了我们类似的东西:

d3.geoConicConformal()
   .rotate([-13.33333,0]
   .parallels([46,49])

现在,由这些限制定义的边界框:

  • 东:17.2 度
  • 西:9.3 度
  • 北:49.2 度
  • 南:46.0​​ 度

.fitExtent(和 .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]
            ]
          ]
        }

记得用right hand rule,终点和起点一样(否则悲痛欲绝)

现在我们所要做的就是调用这个方法,我们就会得到我们的投影。 因为我使用图像来验证我的投影参数,所以我知道我想要的纵横比。如果你不知道宽高比,你可能会有一些多余的宽度或高度。这给了我类似的东西:

var projection = d3.geoConicConformal()
.parallels([46,49])
.rotate([-13.333,0])
.fitSize([width,height],bbox)

还有一个看起来很开心的最终产品(记住一个严重缩减采样的世界 topojson):