BartoNaz
BartoNaz

Reputation: 2893

Dynamic simplification with projection in D3

I am drawing an SVG map with D3 using the d3.geo.mercator() projection.

I also use a zoom behaviour with the map which applies a transform to the <g> object holding all paths of the map.

After looking at examples of dynamic simplification by Mike Bostock (http://bl.ocks.org/mbostock/6252418) I wonder whether I can apply such an algorithm in my case to redraw the geometry with fewer points when it's zoomed out?

In all examples I've seen, there is a simplify function which skips negligible points and plots the rest as it is, and that function is used in var path = d3.geo.path().projection(simplify). I can't use it like that since I need it to be applied on top of already existing projection = d3.geo.mercator().scale(*).translate([*,*]).

How should I use dynamic simplification with existing projection?

Upvotes: 3

Views: 1046

Answers (1)

ffflabs
ffflabs

Reputation: 17481

According to the example you quoted, Dynamic Simplification II

The simplify function would be something like

var simplify = d3.geo.transform({
  point: function(x, y, z) {
    if (z >= area) {
        this.stream.point(x, y);
    }
  }
});

Where area is a treshold variable that you can set beforehand or modify dinamically according to zoom.

Then you would use it on the projection method of d3.geo.path() like

var path = d3.geo.path()
    .projection(simplify);

That's more or less the situation you described in your answer. Now, according to Dynamic Simplification IV, the projection method could also be defined as

var path = d3.geo.path()
    .projection({
        stream: function(s) { 
            return simplify.stream(s); 
        }
     });

This is exactly the same as before. It's just "expanding" the default methods. d3.geo.path always calls the projection stream method, so you can declare your own stream and forward it to simplify.stream.

Now, you say you need to reproject your path using d3.geo.mercator().

var mercatorProjection = d3.geo.mercator().scale(*).translate([*,*]);

No problem: the streams are chainable. You can do:

var path = d3.geo.path()
    .projection({
        stream: function(s) { 
            return simplify.stream(mercatorProjection.stream(s)); 
        }
     });

As well as:

var path = d3.geo.path()
    .projection({
        stream: function(s) { 
            return mercatorProjection.stream(simplify.stream(s)); 
        }
     });

The only difference being that the treshold area would have to be calculated differently if you're dealing with WGS84, pixels or another coordinate system.

Important Caveat, the z parameter in the simplify function isn't the altitude. It is the area of the triangle defined by each point, a precalculated value that's part of TopoJSON sweetness.

I'm afraid this means you can't rely on this example to simplify regular geoJSON. You'll have to add your own logic to calculate each point's related area (if you want to apply Visvalingam's algorithm) or the distance to the closest point (if you want to apply Douglas-Peucker algorithm) or implement your own algorithm.

Good luck.

Upvotes: 4

Related Questions