简体   繁体   中英

How to animate an object along a GeoJSON path using d3.js?

I'm using D3.js to generate and render a path from a GeoJSON file. That works fine, but now I'd like to animate an object along that path. I know how to do that using D3 and standard SVG:

  1. Create a transition and set its duration
  2. For each frame of the transition, use the % complete to find the coordinates along the path
  3. Move the object to the coordinates found in step 2

That's simple. But the problem I'm having is that d3.geo.path() doesn't seem to return any length or position data like a standard D3 path object (such as the helpful getPointAtLength() method). So I'm unable to find the x,y coordinates of a point at, say, 25% along the path.

Is there a way to obtain this data? (Or is there a better way, such as converting the d3.geo.path() to a regular D3 path?)

Below is a truncated version of my code; a live example is here: http://jsfiddle.net/5m35J/4/

json = {
    ... // snipped for brevity
};

// Draw a GeoJSON line on the map:

map = $('#map');
xy = d3.geo.mercator().scale(480000).translate([630700, 401100]);
path = d3.geo.path().projection(xy);

vis = d3.select("#map")
    .append("svg:svg")
    .attr("width", 960)
    .attr("height", 600);

vis.append("svg:g")
    .attr("class", "route")
    .selectAll("path")
    .data(json.features)
    .enter()
    .append("svg:path")
    .attr("d", path)
    .attr("fill-opacity", 0.5)
    .attr("fill", "#fff")
    .attr("stroke", "#333");

// Draw a red circle on the map:

//len = 100; // how do I find the length of the path?
origin_x = 100;
origin_y = 100;

group = vis.append("svg:g");

circle = group.append("circle")
    .attr({
    r: 10,
    fill: '#f33',
    transform: function () {
        //var p = path.getPointAtLength(0)
        //return "translate(" + [p.x, p.y] + ")";
        return "translate("+ origin_x +","+ origin_y +")";
    }
});

// Animate the circle:

duration = 5000;
circle.transition()
    .duration(duration)
    .ease("linear")
    .attrTween("transform", function (d, i) {
    return function (t) {
        //var p = path.node().getPointAtLength(len*t) // d3.geo.path() doesn't provide a getPointAtLength() method!
        //return "translate("+[p.x,p.y]+")"
        var current_x = origin_x + origin_x * t;
        var current_y = origin_y + origin_y * t;            
        return "translate("+ current_x +","+ current_y +")";
    }
});

Well, I figured it out, but I'm not completely sure if my solution is the "right" way to do it. Basically, I used D3 to select the raw SVG elements that were created by the d3.geo.path() object.

Note the changes to the targetPath , pathNode , and pathLength variables, and also to the transform() and attrTween() functions:

// Draw a red circle on the map:

group = vis.append("svg:g");

var targetPath = d3.selectAll('.route')[0][0],
    pathNode = d3.select(targetPath).selectAll('path').node(),
    pathLength = pathNode.getTotalLength();

circle = group.append("circle")
    .attr({
    r: 10,
    fill: '#f33',
    transform: function () {
        var p = pathNode.getPointAtLength(0)
        return "translate(" + [p.x, p.y] + ")";
    }
});

// Animate the circle:

duration = 10000;
circle.transition()
    .duration(duration)
    .ease("linear")
    .attrTween("transform", function (d, i) {
    return function (t) {
        var p = pathNode.getPointAtLength(pathLength*t);
        return "translate(" + [p.x, p.y] + ")";
    }
});

Live example is here: http://jsfiddle.net/5m35J/6/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM