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

How to plot longitude latitude coordinates for geoAlbersUsa() projection?

我正在尝试创建一个简单的气泡图,它使用美国国土安全部数据根据一个县的一些入学人数绘制经纬度对。我的 CSV 文件包含县名、经度、纬度、入学类型、入学 class、入学人数和原籍国。我创建了一些复选框,允许用户查看被美国录取的不同 classes 录取的气泡图。

我了解到 d3geoAlbersUsa 投影将美国地图投影在 [0,0] 处,该地图位于非洲海岸附近。从下面的照片(参见 Imgur link)中,您可以看到我的点似乎绘制在正确的坐标上。但是,背景地图不可见。当我使用 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" width="500" height="500"></svg> -->
<div id="my_dataviz"></div>

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

我的气泡图的样子

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

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

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

如果您有 features/tiles/raster 显示美国所在的非洲,反之亦然,则您的投影或坐标系存在冲突,而不是 d3.geoAlbersUsa 的失败。

此外,如果您将预投影几何体和未投影几何体混合在一起,那就太难了:您需要确保用于预投影一个几何体的投影与用于投影第二个的投影,除非您使用 d3 在某处离线预投影几何体,否则会让您有些头疼。

如果你有一个坐标为十进制的 csv 和一个具有相同坐标的 geojson,你可以假设投影(以及因此,任何路径)将被一致地正确渲染(其中正确不一定等同于根据需要...)。

现在 d3.geoAlbersUsa 有点特殊,因为它是一个组合投影,结合了几个不同的投影(所有 Albers,同时也将阿拉斯加缩小了几次)。它被校准为以美国为中心,假设屏幕尺寸为 960x600 像素。您不需要更改中心点,它已经为您设置好了。但是,必须根据您的情况修改翻译,因为这会翻译投影坐标。默认翻译需要一个 960x600 像素的容器。您希望翻译等于 width/2、height/2。更棘手的是比例,默认比例为 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>

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