d3 line and points on map from csv data

南笙酒味 提交于 2019-12-13 12:11:51

问题


I am trying to plot individual data points and also a line path that runs between these points like this example D3 stop and restart transition along path to allow click through to geo data coordinates that I have been helped with before.

Now I want to use my actual data rather than a test set of coordinates but am running into trouble. I have tried both a geoJson file and also csv for my data. I am using a csv file with lon and lat for the points and was hoping to make the line from that same set of data, ie use one set of data for the points and line.

I can’t get my line to show up in the correct place - it is in top right corner but should be on / through points. I think this is something to do with projection but I am having trouble parsing the data correctly to get a line string as required. I have tried to use the sample here https://bl.ocks.org/alandunning/cfb7dcd7951826b9eacd54f0647f48d3 - but get empty objects??

My question is how to use the csv lon lat with a d3 svg line generator. This is my code:

<!DOCTYPE html>
<html lang="en">
    <head>
<meta charset="utf-8">
<title>Working version 3</title>

<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-array.v1.min.js"></script>
<script src="https://d3js.org/d3-geo.v1.min.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<style type="text/css">

circle {
  fill: steelblue;
  stroke: pink;
  stroke-width: 3px;
}


.line{
  fill: none;
  stroke: red;
  stroke-width: 6;

}

</style>
</head>
<body>

<script>

    var w = 960,
        h = 500;

    var projection = d3.geoMercator()
                         .translate([w/2, h/2])
                         .scale([w * 0.16]);

    var path = d3.geoPath()
                 .projection(projection);

    var duration = 10000;

    var svg = d3.select("body").append("svg")
        .attr("width", w)
        .attr("height", h);

/*
    var line = d3.line()
            .x(function (d) {return projection([d.lon]);})
            .y(function (d) {return projection([d.lat]);})
    .curve(d3.curveBasis);

    var line = d3.line()
    .x(function(d){return projection(d[0].lon);})
    .y(function(d){return projection(d[0].lat);})
    .curve(d3.curveBasis);

/*ok line shows up but in wrong place
    var line = d3.line()
    .x(function(d) { return (d.lon); })
    .y(function(d) { return (d.lat); })
        .curve(d3.curveBasis);
*/

var line = d3.line()
    .x(function(d) { return (d.lon); })
    .y(function(d) { return (d.lat); })
    .curve(d3.curveBasis);


//original
/*
var line = d3.line()
.x(function(d){return projection(d)[0];})
.y(function(d){return projection(d)[1];})
.curve(d3.curveBasis);
*/
//

//bring in data
d3.queue()
    .defer(d3.json, "data/oceans.json")
    .defer(d3.csv, "data/speckCities.csv")
    .await(ready);

function ready (error, oceans, data){
    if (error) throw error;

    //console.log(data[0]);
    //console.log(data[0].lon);




//map
svg.selectAll("path")
        .data(oceans.features)
        .enter()
        .append("path")
        .attr("d", path)
        .style("fill", "#A8B2C3");

var linepath = svg.append("path")
  .datum(data)
  .attr("d", line)
    .attr('class', 'line');

    svg.selectAll("circle")
         .data(data)
         .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", 5)
         .style("fill", "yellow")
         .style("stroke", "gray")
         .style("stroke-width", 0.25)
         .style("opacity", 0.75)
         .append("title")           //Simple tooltip
         .text(function(d) {
            return d.name ;
         });

    //




    //

    /*svg.selectAll(".point")
      .data(coordinates)
        .enter()
        .append("circle")
      .attr("r", 7)
      .attr("transform", function(d) { return "translate(" + projection(d) + ")"; });


var circle = svg.append("circle")
  .attr("r", 19)
  .attr("transform", "translate(" + projection(d) + ")");

/*
var pauseValues = {
        lastT: 0,
        currentT: 0
        };

function transition() {
  circle.transition()
      .duration(duration - (duration * pauseValues.lastT))
      .attrTween("transform", translateAlong(linepath.node()))
      .on("end", function(){
        pauseValues = {
          lastT: 0,
          currentT: 0
        };
        transition()
      });
}

function translateAlong(path) {
  var l = path.getTotalLength();
  return function(d, i, a) {
    return function(t) {
      t += pauseValues.lastT;
      var p = path.getPointAtLength(t * l);
      pauseValues.currentT = t;
      return "translate(" + p.x + "," + p.y + ")";
    };
  };
}

d3.select('button').on('click',function(d,i){
  var self = d3.select(this);
  if (self.text() == "Pause"){
        self.text('Play');
        circle.transition()
      .duration(0);
        setTimeout(function(){
            pauseValues.lastT = pauseValues.currentT;
        }, 100);
  }else{
    self.text('Pause');
    transition();
  }
});
*/
}

</script>
</body>
</html>

回答1:


You are not projecting your line:

var linepath = svg.append("path")
  .datum(data)
  .attr("d", line)
   .attr('class', 'line');

In this case your longitude/latitude pairs in your geojson are converted to straight pixel coordinates:

var line = d3.line()
    .x(function(d) { return (d.lon); })
    .y(function(d) { return (d.lat); })
    .curve(d3.curveBasis);

As svg coordinates start at [0,0] in the top left, and your points appear to be around 10 degrees east or so (positive longitude), and 50 degrees north or so (positive latitude), your first point in the line appears 10 pixels from the left and 50 pixels from the top. Also, because svg y values increase as one moves down, but latitude values increase as one moves north (typcally up on a map), your line also appears inverted on the y axis compared to your points.

You could set your line function to use a projection to set the x and y points:

var line = d3.line()
    .x(function(d) { return projection([d.lon,d.lat])[0] ; })
    .y(function(d) { return projection([d.lon,d.lat])[1]; })
    .curve(d3.curveBasis);

You need both latitude and longitude to project a point, so the projection function takes both, and returns both x and y, hence the [0] and [1], this is why your commented out sections don't work

But this is unnecessary, you can pass geojson straight to the path (the same as you do for the world background), that is if your data is available in geojson (though it is not hard to make a geojson on the fly):

var linepath = svg.append("path")
  .datum(data) // in geojson form
  .attr("d", path)  // use your path 
  .attr('class', 'line');

This is more accurate than a line - the segments between lines in a d3.line are straight or follow a predefined curve, in Cartesian coordinate space. A d3.geoPath follows great circle distance, so the segments between points follow the shortest path on the planet, a more accurate representation, thought at times perhaps, less stylistic.

To create the geojson on the fly, assuming your data looks like: [{lon:number,lat:number},{lon:number,lat:number}] you could use something like:

var points = data.map(function(d) { return [d.lon,d.lat] })
var geojson = { "type": "LineString", "coordinates": points }


来源:https://stackoverflow.com/questions/47917730/d3-line-and-points-on-map-from-csv-data

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!