如何绘制 geoAlbersUsa() 投影的经纬度坐标?

Posted

技术标签:

【中文标题】如何绘制 geoAlbersUsa() 投影的经纬度坐标?【英文标题】:How to plot longitude latitude coordinates for geoAlbersUsa() projection? 【发布时间】:2021-04-09 07:33:59 【问题描述】:

我正在尝试创建一个简单的气泡图,该气泡图使用美国 DHS 数据绘制一个县的许多招生人数的经纬度对。我的 CSV 文件包含县名、经度、纬度、录取类型、录取类别、录取数量和原籍国。我创建了一些复选框,允许用户查看被美国录取的不同类别录取的气泡图。

我了解到,d3geoAlbersUsa 投影将美国地图投影在非洲海岸附近的 [0,0]。从下面的照片(请参阅 Imgur 链接)中,您可以看到我的点似乎绘制在正确的坐标上。但是,背景图是不可见的。当我使用 d3.geoMercator() 并将投影居中 [0,0] 时,我看到了地图。在这两种情况下,我都不知道如何让气泡出现在地图上。

我是 d3 的新手,所以我不知道该怎么做。如何使用带有 d3geoAlbersUsa 投影的长纬度坐标创建气泡图?感谢您的帮助。

这是我的 index.html

var width = 750
var height = 750

// The svg
var svg = d3.select("#my_dataviz")
  .append("svg")
  .attr("width", width)
  .attr("height", height)

// Map and projection
// Map and projection
// var projection = d3.geoMercator()
//     //.center([-100, 30])                // GPS of location to zoom on
//     .center([0, 0]) 
//     .scale(200)                       // This is like the zoom
//     .translate([ width/2, height/2 ])

var projection = d3.geoAlbersUsa()
  //.center([0,0])
  .scale([1000]) // This is like the zoom
  .translate([width / 2, height / 2])

var data = d3.csv("sheet.csv", function(data) 

  var markers = data.filter(function(d) 

    if (
      (d["MajorClassAdmission"] == "EmploymentPreference1st" ||
        d["MajorClassAdmission"] == "EmploymentPreference2nd" ||
        d["MajorClassAdmission"] == "EmploymentPreference3rd") &&
      d["CountryofBirth"] == "Bangladesh" && d["Admissions"] != "D" && d["lon"] != "NA" && d["lat"] != "NA") 
      return d;
    
  )
  //console.log(markers)

  // Load external data and boot
  //d3.json("projectedgeography.json", function(data)
  d3.json("projectedgeography.geojson", function(data) 
    //d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson", function(data)

    // Filter data
    //data.features = data.features.filter( function(d)return d.properties.name=="USA" )
    //data.features = data.features.filter( function(d)return d.properties.name=="USA" )

    // Create a color scale
    var color = d3.scaleOrdinal()
      .domain(["EmploymentPreference1st", "EmploymentPreference2nd", "EmploymentPreference3rd"])
      .range(["#402D54", "#D18975", "#8FD175"])

    // Add a scale for bubble size
    var size = d3.scaleLinear()
      .domain([1, 100]) // What's in the data
      .range([4, 50]) // Size in pixel

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

    //Draw the map
    svg.append("g")
      .selectAll("path")
      .data(data.features)
      .enter()
      .append("path")
      .style("stroke", "#black")
      .style("opacity", .3)


    //create a tooltip (hover information)
    var Tooltip = d3.select("#my_dataviz")
      .append("div")
      .attr("class", "tooltip")
      .style("opacity", 1)
      .style("background-color", "white")
      .style("border", "solid")
      .style("border-width", "2px")
      .style("border-radius", "5px")
      .style("padding", "5px")

    // Three function that change the tooltip when user hover / move / leave a cell
    var mouseover = function(d) 
      Tooltip.style("opacity", 1)
    
    var mousemove = function(d) 
      Tooltip
        .html(d.CountyState + "<br>" + "long: " + d.lon + "<br>" + "lat: " + d.lat + "<br>" + "Admissions: " + d.Admissions)
        .style("left", (d3.mouse(this)[0] + 10) + "px")
        .style("top", (d3.mouse(this)[1]) + "px")
    
    var mouseleave = function(d) 
      Tooltip.style("opacity", 0)
    

    // Add circles:
    svg
      .selectAll("myCircles")
      .data(markers)
      .enter()
      .append("circle")
      .attr("class", function(d) 
        return (d.MajorClassAdmission)
      )
      .attr("cx", function(d) 
        return projection([d.lon, d.lat])[0]
      )
      .attr("cy", function(d) 
        return projection([d.lon, d.lat])[1]
      )
      .attr("r", function(d) 
        return d.Admissions
      )
      .style("fill", function(d) 
        return color(d.MajorClassAdmission)
      )
      .attr("stroke", function(d) 
        return color(d.MajorClassAdmission)
      )
      .attr("stroke-width", 3)
      .attr("fill-opacity", .4)
      .on("mouseover", mouseover)
      .on("mousemove", mousemove)
      .on("mouseleave", mouseleave)

    // This function is gonna change the opacity and size of selected and unselected circles
    function update() 

      // For each check box:
      d3.selectAll(".checkbox").each(function(d) 
        cb = d3.select(this);
        group = cb.property("value")
        //console.log(group)

        // If the box is check, I show the group
        if (cb.property("checked")) 
          //console.log("checked")
          svg.selectAll("." + group).transition().duration(1000).style("opacity", 1).attr("r", function(d) 
            return d.Admissions
          )

          // Otherwise I hide it
         else 
          //console.log("unchecked")
          svg.selectAll("." + group).transition().duration(1000).style("opacity", 0).attr("r", 0)
        
      )
    

    // When a button change, I run the update function
    d3.selectAll(".checkbox").on("change", update)

    // And I initialize it at the beginning
    update()
  )

)
<!DOCTYPE html>
<html>

<meta charset="utf-8">
<style>
  .circle:hover 
    stroke: black;
    stroke-width: 4px;
  
  
  .legend circle 
    fill: none;
    stroke: #ccc;
  
  
  .legend text 
    fill: #777;
    font: 10px sans-serif;
    text-anchor: middle;
  
</style>

<!-- Load d3.js and the geo projection plugin -->
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script src="https://d3js.org/queue.v1.min.js"></script>
<!-- <script src="http://d3js.org/d3.geo.projection.v0.min.js"></script> optional, depending on projection -->

<h1>LPR Top 200 Bangladesh</h1>

<!-- Button -->
<div>
  <input type="checkbox" class="checkbox" value="EmploymentPreference1st" checked><label>Employment Preference 1</label>
  <input type="checkbox" class="checkbox" value="EmploymentPreference2nd" checked><label>Employment Preference 2</label>
  <input type="checkbox" class="checkbox" value="EmploymentPreference3rd" checked><label>Employment Preference 3</label>
</div>

<!-- Create an element where the map will take place -->
<!-- <svg id="my_dataviz"  ></svg> -->
<div id="my_dataviz"></div>

附件是我的数据截图以及使用 d3.geoMercator() 和 d3.geoAlbersUsa() 时气泡图的样子

截图如下:https://imgur.com/gallery/dRghqAf

编辑:看起来我使用了错误的 geojson。我最初是从 US Census 下载 shapefile 并使用 Mapshaper 转换为 geojson 文件。使用那个geojson,我使用了一个投影应用程序来创建一个带有geoalbersUsa 投影的geojson。实际上,我在已经转换为该投影的 geojson 上使用了 d3.geoAlbersUsa()。我误解了 geoAlbersUsa() 的工作原理。使用 Mapshaper 的原始 geojson,我得到了我正在寻找的地图:https://imgur.com/gallery/9sj68SM

【问题讨论】:

通常“图书馆”会为您计算十进制度数的投影。不是这样吗? @GetSet 你能详细说明一下吗?如果我使用常规投影(geoMercator),坐标会显示在地图上 您的图像显示背景图块,但这不会出现在您的代码中。根据文件名,您似乎也在使用投影几何。您能否澄清我们所有的源数据是否都是经纬度对?此外,您仅对如何绘制点以外的特征进行了评论,您能否确认用于绘制这些特征的投影和路径?如果使用瓦片或其他背景栅格数据,您能说明一下您是如何做到的吗? 如果您可以共享 csv 的 sn-p(不是屏幕截图)和 geojson 文件,那么演示如何在地图中将两者结合起来是最简单的。 【参考方案1】:

D3 投影都非常相似。如果一个投影正确地投影给定点,那么每隔一个投影也可能如此。它们采用十进制度数坐标,并以像素为单位输出坐标,仅此而已。因此,您的声明:“d3geoAlbersUsa 投影将美国地图投影在非洲海岸附近的[0,0]。”是不正确的。 [0,0] 以度为单位位于非洲海岸外,[0,0] 以像素为单位可以在任何地方。 D3 给你的是像素,而不是度数。

如果您的要素/图块/栅格可以显示美国所在的非洲,反之亦然,则您的投影或坐标系存在冲突,而不是 d3.geoAlbersUsa 失败。

此外,如果您要混合预先投影的几何图形和未投影的几何图形,那您就太难了:您需要确保用于预先投影一个几何图形的投影与用于投影的投影相匹配第二个,除非您使用 d3 在某处离线预投影几何图形,否则您会很头疼。

如果您有一个坐标以十进制度为单位的 csv 和一个具有相同坐标的 geojson,您可以假设投影(以及因此任何路径)将一致且正确地呈现(其中正确不一定等于所需...)。

现在 d3.geoAlbersUsa 有点特殊,因为它是一个组合投影,结合了几个不同的投影(所有阿尔伯斯,同时也将阿拉斯加缩小了几次)。假设屏幕尺寸为 960x600 像素,它被校准为以美国为中心。您不需要更改中心点,它已经为您设置好了。但是,必须根据您的情况修改转换,因为这会转换投影坐标。默认翻译需要一个 960x600 像素的容器。您希望平移等于 width/2,height/2,就像您一样。 Trickier 是比例,默认比例是 1070,将美国扩展到 960 像素。这个比例因子是线性的,所以我们可以使用:1070/960*width 来创建一个新的比例因子(假设宽度是限制因子)。

d3.geoMercator 更简单,但我们需要正确地使该投影居中,因为默认情况下它不是以美国为中心。我们可以使用:

d3.geoMercator()
  .center([-100,30]) // as you had
  .scale(1000) // or whatever
  .translate([width/2,height/2])

我们仍然需要应用翻译,因为默认值不期望正方形 750x750 svg。

注意还有 projection.fitSize() 会自动调整比例并转换为 geojson 特征的中心,如下所示:

 d3.geoMercator()
   .fitSize([width,height],geojson) // geojson must be a valid geojson object, not an array of geojson features.

我们将投影传递给路径生成器,然后相对直接地绘制点和geojson:

以下使用注释掉的行访问geojson,如果您分享projectedgeography.geojson,我也许可以为您提供一些有关您的代码为何无法按预期工作的信息。但考虑到它的名字,它看起来肯定不是非投影数据

阿尔伯斯美国

var width = 750
var height = 400
    
// The svg
var svg = d3.select("#my_dataviz")
  .append("svg")
  .attr("width", width)
  .attr("height", height);
  
// Create a color scale
var color = d3.scaleOrdinal()
  .domain(["A", "B", "C" ])
  .range([ "#402D54", "#D18975", "#8FD175"])  
  
var projection = d3.geoAlbersUsa()
  .translate([width/2,height/2])
  .scale(1070/960*width); // scale the scale factor, otherwise map will overflow SVG bounds.
  
var path = d3.geoPath(projection);

d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson", function(geojson)

    geojson.features = geojson.features.filter( function(d)return d.properties.name=="USA" )


 // d3.csv("file.csv", function(csv) 
 // As I cannot access your csv, I'm reproducing a few lines here:
    var csv = [
      lon: -116.2,lat: 43.5, Admissions: 24, MajorClassAdmission: "A",
      lon: -81.7,lat: 41.4, Admissions: 13, MajorClassAdmission: "B",
      lon: -74.1,lat: 40.9, Admissions: 35, MajorClassAdmission: "C",
      lon: -121.6,lat: 37.3, Admissions: 14, MajorClassAdmission: "B",
      lon: -73.9,lat: 40.68, Admissions: 13, MajorClassAdmission: "A",
    ]
 
 
    svg.selectAll("path")
      .data(geojson.features)
      .enter()
      .append("path")
      .attr("d", path);

    svg.selectAll("circle")
      .data(csv)
      .enter()
      .append("circle")
      .attr("cx", function(d) 
         return projection([d.lon,d.lat])[0];
      )
      .attr("cy", function(d) 
        return projection([d.lon,d.lat])[1];
      )
      .attr("r", function(d) 
        return d.Admissions
      )
      .attr("fill", function(d) 
        return color(d.MajorClassAdmission);
      )
      // ...

//  )
)
.circle:hover
    stroke: black;
    stroke-width: 1px;

path 
  fill: none;
  stroke: #ccc;
  stroke-width: 1px;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>

墨卡托

var width = 750
var height = 400
    
// The svg
var svg = d3.select("#my_dataviz")
  .append("svg")
  .attr("width", width)
  .attr("height", height);
  
// Create a color scale
var color = d3.scaleOrdinal()
  .domain(["A", "B", "C" ])
  .range([ "#402D54", "#D18975", "#8FD175"])  
  
var projection = d3.geoMercator()
  
var path = d3.geoPath(projection);

d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson", function(geojson)

    geojson.features = geojson.features.filter( function(d)return d.properties.name=="USA" )

   projection.fitSize([width,height],geojson)

 // d3.csv("file.csv", function(csv) 
 // As I cannot access your csv, I'm reproducing a few lines here:
    var csv = [
      lon: -116.2,lat: 43.5, Admissions: 24, MajorClassAdmission: "A",
      lon: -81.7,lat: 41.4, Admissions: 13, MajorClassAdmission: "B",
      lon: -74.1,lat: 40.9, Admissions: 35, MajorClassAdmission: "C",
      lon: -121.6,lat: 37.3, Admissions: 14, MajorClassAdmission: "B",
      lon: -73.9,lat: 40.68, Admissions: 13, MajorClassAdmission: "A",
    ]
 
 
    svg.selectAll("path")
      .data(geojson.features)
      .enter()
      .append("path")
      .attr("d", path);

    svg.selectAll("circle")
      .data(csv)
      .enter()
      .append("circle")
      .attr("cx", function(d) 
         return projection([d.lon,d.lat])[0];
      )
      .attr("cy", function(d) 
        return projection([d.lon,d.lat])[1];
      )
      .attr("r", function(d) 
        return d.Admissions
      )
      .attr("fill", function(d) 
        return color(d.MajorClassAdmission);
      )
      // ...

//  )
)
.circle:hover
    stroke: black;
    stroke-width: 1px;

path 
  fill: none;
  stroke: #ccc;
  stroke-width: 1px;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>

虽然您的代码中的 cmets 混淆了您用来生成有问题的地图的代码,但您的代码中有一些潜在的简化:由于使用了 d3.geo,您似乎同时使用了 d3v4 和 d3v3。路径 (v3) 而不是 d3.geoPath (v4+)。基础 d3 包包含您需要的所有地理功能,因此您无需导入 https://d3js.org/d3-geo-projection.v2.min.js 或任何其他模块。

【讨论】:

以上是关于如何绘制 geoAlbersUsa() 投影的经纬度坐标?的主要内容,如果未能解决你的问题,请参考以下文章

怎样把坐标变成经纬度

如何将经纬度坐标中的栅格投影到 UTM,以便在 tmap 中绘图?

delphi根据经纬度画地图

北京54坐标系(高斯投影)转经纬度

西安80坐标系(高斯投影)转经纬度

ArcMap 10.0使用问题:已知一点的经纬度,如何在China_Lambert_Conformal_Conic下投影?