Flavio
Flavio

Reputation: 1587

D3 cardinal line interpolation looks wrong

I'm trying to give a polygon - drawn with d3 - smooth edges using the d3.svg.line().interpolate() option but I get strange looking results.

I receive the polygon data from the nokia HERE api as world coordinate data in the form [lat1, long1, alt1, lat2, long2, alt2 ...] So in the routingCallback function - which is called when the response is in - I first refine it so it looks like this [[lat1, long1], [lat2, long2] ...]. In d3.svg.line() I then use this array of coordinates to calculate the pixel positions. Im using Leaflet to draw the polygon on a map so I use the map.latLngToLayerPoint() function to do that. The actual drawing of the polygon happens in reset() which is called from the routingCallback immediately after the data is available and every time the map gets zoomed

var map = new L.Map("map", {"center": [52.515, 13.38], zoom: 12})
    .addLayer(new L.TileLayer('http://{s}.tile.cloudmade.com/---account key---/120322/256/{z}/{x}/{y}.png'));

map.on("viewreset", reset);


var svg = d3.select(map.getPanes().overlayPane).append("svg"),
    g = svg.append("g").attr("class", "leaflet-zoom-hide group-element"),

bounds = [[],[]],
polygon,
refinedData,

line = d3.svg.line()
    .x(function(d) {                
        var location = L.latLng(d[0], d[1]),
            point = map.latLngToLayerPoint(location);
        return point.x; 
    })
    .y(function(d) {
        var location = L.latLng(d[0], d[1]),
            point = map.latLngToLayerPoint(location);
        return point.y;
    })
    .interpolate("cardinal"),

routingCallback = function(observedRouter, key, value) {
    if(value == "finished") {
        var rawData = observedRouter.calculateIsolineResponse.isolines[0].asArray(),
        refinedData = [];

        for(var i = 2; i < rawData.length; i += 3) {
            var lon = rawData[i-1],
                lat = rawData[i-2];

            refinedData.push([lat, lon]); 
        }

    if(polygon)
        polygon.remove();

    polygon = g
        .data([refinedData])
        .append("path")
            .style("stroke", "#000")
            .style("fill", "none")
            .attr("class", "isoline");             

        reset(); 
    }
    if(value == "failed") {   
        console.log(observedRouter.getErrorCause());
    }
};


getIsolineData = function(isoline) {

    return data;
};

function reset() {
    var xExtent = d3.extent(refinedData, function(d) {
        var location = L.latLng(d[0], d[1]);
        var point = map.latLngToLayerPoint(location);
        return point.x;
    });

    var yExtent = d3.extent(refinedData, function(d) {
        var location = L.latLng(d[0], d[1]);
        var point = map.latLngToLayerPoint(location);
        return point.y;
    });

    bounds[0][0] = xExtent[0];
    bounds[0][1] = yExtent[0];
    bounds[1][0] = xExtent[1];
    bounds[1][1] = yExtent[1];

    var topLeft = bounds[0],
        bottomRight = bounds[1];

    svg .attr("width", bottomRight[0] - topLeft[0])
        .attr("height", bottomRight[1] - topLeft[1])
        .style("left", topLeft[0] + "px")
        .style("top", topLeft[1] + "px");

    g   .attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");

    polygon.attr("d", line);
}

I expect this to produce smooth edges but instead I get a small loop at every corner. The red overlay is the same polygon without interpolation. There are only points at the corners. No points added inbetween.

strange looking corners when using d3 cardinal interpolation. red overlay is the same polygon without interpolation

Does it have something to do with the order of the points (clockwise/counter clockwise)? I tried to rearrange the points but nothing seemed to happen.

Upvotes: 1

Views: 1571

Answers (1)

AmeliaBR
AmeliaBR

Reputation: 27534

The only way I can recreate the pattern you're getting is if I add every vertex to the path twice. That wouldn't be noticeable with a linear interpolation, but causes the loops when the program tries to connect points smoothly.

http://fiddle.jshell.net/weuLs/

Edit:

Taking a closer look at your code, it looks like the problem is in your calculateIsolineResponse function; I don't see that name in the Leaflet API so I assume it's custom code. You'll need to debug that to figure out why you're duplicating points.

If you can't change that code, the simple solution would be to run your points array through a filter which removes the duplicated points:

refinedData = refinedData.filter(function(d,i,a){
        return ( (!i) || (d[0] != a[i-1][0]) || (d[1] != a[i-1][1]) ); 
     });

That filter will return true if either it's the first point in the array, or if either the lat or lon value is different from the previous point. Duplicated points will return false and be filtered out of the array.

Upvotes: 3

Related Questions