如何使 geoAlbersUSA 投影像 geoMercator 一样笔直(不弯曲)?

How to make geoAlbersUSA projection straight(not curved) like geoMercator?

我按照指南在 d3js 中创建了美国的基本地图

指南 link - http://bl.ocks.org/michellechandra/0b2ce4923dc9b5809922.


我已经了解 GeoJSON 和地图投影的基础知识。

目前我的地图是这样的 -(地图 1) - 这使用 d3.geoAlbersUsa() 投影

我希望我的地图看起来像这样 -(地图 2) - 即顶部不是弯曲的而是直线边界

link https://www.worldatlas.com/webimage/countrys/namerica/usstates/usa50mer.htm

要求 -

  1. 我想让阿拉斯加和夏威夷在地图上像两张图片一样可见

我已经尝试了什么-

  1. 我曾尝试使用 d3.geoMercator() 投影,但阿拉斯加太大而无法放入视口并且位于我不想要的左上角。

你无法矫正阿尔伯斯。阿尔伯斯投影是圆锥形投影 - 平行线的大小不同,因为它们被投影到圆锥上,这意味着由于投影长度不同(平行线在地球上的长度也不同),因此会有一条曲线。

圆柱墨卡托投影拉伸较短的纬线以占据与赤道相同的宽度,这就是为什么在墨卡托投影中纬线是直线和水平的且长度相等的原因。

我们可以滥用 Albers 投影来消除一些曲线,但 d3 的 geoAlbersUsa 投影是一个复合投影,不允许进行此类修改。它实际上只是投影的集合。

相反,我们可以使用 d3.geoTransform 和 d3.geoMercator 创建我们自己的复合投影。结果应如下所示:

这是一个简单的 d3 geoTransform:

  let projection = d3.geoTransform({
      point: function(x, y) {
          this.stream.point(x*2,y*2);
      }
    });

这只是将 x 和 y 值乘以 2,它可以像这样传递给路径生成器:d3.geoPath(projection);

要在这里制作复合投影,我们需要正确设置三个投影,然后将其正确平移到地图上。我在假设 960x500 地图(类似于 d3.geoAlbersUsa)的情况下构建了这些投影:

  let scale = 800;
  let width = 960;
  let height = 500;

  let continental = d3.geoMercator()
    .center([-98.58,39.83])
    .translate([width*0.5,height*0.42])  // placed near center
    .scale(scale);

  let hawaii = d3.geoMercator()
    .center([-157.25,20.8])
    .scale(scale)
    .translate([width*0.35,height*0.87])  // placed near bottom, slightly left of center

  let alaska = d3.geoMercator()
    .center([-152.5,65])
    .translate([width*0.15,height*0.8])    // placed in lower left
    .scale(scale*0.3)

当一个人移动到极端纬度时,墨卡托确实夸大了尺寸,因此阿拉斯加在地理上已经很大(超过德克萨斯州的两倍)在投影时变得 很多 太大了。我已经按比例缩小它以使其适合(这样它大约是预计尺寸的 1/10)。

我们还需要选择何时使用什么投影,因此根据一些规则我们可以确定某个点是在阿拉斯加、夏威夷还是较低的 48:

  let projection = d3.geoTransform({
      point: function(x, y) {
        if(y < 50 && x < -140) {  // south and west : hawaii
          this.stream.point(...hawaii([x,y]));
        }
        else if (y > 50) {       // north : alaska
          this.stream.point(...alaska([x,y]));
        }
        else {                    // otherwise, it's in the lower 48.
          this.stream.point(...continental([x,y]));
        }
      }
    });

就是这样,我们有三个投影和一种方法来确定应该为给定点使用哪一个。

我将把它全部打包为 d3.geoMercatorUsa() 以使其更加独立,我们得到:

// Composite Mercator projection for the US
d3.geoMercatorUsa = function() {
  let scale = 800;
  let width = 960;
  let height = 500;

  let continental = d3.geoMercator()
    .center([-98.58,39.83])
    .translate([width*0.5,height*0.42])
    .scale(scale);

  let hawaii = d3.geoMercator()
    .center([-157.25,20.8])
    .scale(scale)
    .translate([width*0.35,height*0.87])

  let alaska = d3.geoMercator()
    .center([-152.5,65])
    .translate([width*0.15,height*0.8])
    .scale(scale*0.3)

  let projection = d3.geoTransform({
      point: function(x, y) {
        if(y < 50 && x < -140) {
          this.stream.point(...hawaii([x,y]));
        }
        else if (y > 50) {
          this.stream.point(...alaska([x,y]));
        }
        else {
          this.stream.point(...continental([x,y]));
        }
      }
    });
    return projection;
}

// Demonstration:
let width = 960;
let height = 500;

let svg = d3.select("body")
  .append("svg")
  .attr("width",width)
  .attr("height",height);

let path = d3.geoPath(d3.geoMercatorUsa());  
  
d3.json("https://raw.githubusercontent.com/johan/world.geo.json/master/countries/USA.geo.json").then(function(data) {

   svg.append("path")
     .datum(data)
     .attr("d",path);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>